Implement correct password verification of Umlaunts, kyrillic, greek.. charactersets, CJK-Characters, and special characters (#2964)

This commit is contained in:
Ozzie Isaacs 2024-02-29 12:08:58 +01:00
commit 14b578dd3a
12 changed files with 55 additions and 44 deletions

View File

@ -125,13 +125,6 @@ def create_app():
ub.password_change(cli_param.user_credentials)
if not limiter:
log.info('*** "flask-limiter" is needed for calibre-web to run. '
'Please install it using pip: "pip install flask-limiter" ***')
print('*** "flask-limiter" is needed for calibre-web to run. '
'Please install it using pip: "pip install flask-limiter" ***')
web_server.stop(True)
sys.exit(8)
if sys.version_info < (3, 0):
log.info(
'*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, '
@ -141,13 +134,6 @@ def create_app():
'please update your installation to Python3 ***')
web_server.stop(True)
sys.exit(5)
if not wtf_present:
log.info('*** "flask-WTF" is needed for calibre-web to run. '
'Please install it using pip: "pip install flask-WTF" ***')
print('*** "flask-WTF" is needed for calibre-web to run. '
'Please install it using pip: "pip install flask-WTF" ***')
web_server.stop(True)
sys.exit(7)
lm.login_view = 'web.login'
lm.anonymous_user = ub.Anonymous
@ -158,13 +144,21 @@ def create_app():
calibre_db.init_db()
updater_thread.init_updater(config, web_server)
# Perform dry run of updater and exit afterwards
# Perform dry run of updater and exit afterward
if cli_param.dry_run:
updater_thread.dry_run()
sys.exit(0)
updater_thread.start()
for res in dependency_check() + dependency_check(True):
requirements = dependency_check()
for res in requirements:
if res['found'] == "not installed":
message = ('Cannot import {name} module, it is needed to run calibre-web, '
'please install it using "pip install {name}"').format(name=res["name"])
log.info(message)
print("*** " + message + " ***")
web_server.stop(True)
sys.exit(8)
for res in requirements + dependency_check(True):
log.info('*** "{}" version does not meet the requirements. '
'Should: {}, Found: {}, please consider installing required version ***'
.format(res['name'],

View File

@ -1834,6 +1834,7 @@ def _configuration_update_helper():
_config_checkbox(to_save, "config_password_number")
_config_checkbox(to_save, "config_password_lower")
_config_checkbox(to_save, "config_password_upper")
_config_checkbox(to_save, "config_password_character")
_config_checkbox(to_save, "config_password_special")
if 0 < int(to_save.get("config_password_min_length", "0")) < 41:
_config_int(to_save, "config_password_min_length")

View File

@ -165,6 +165,7 @@ class _Settings(_Base):
config_password_number = Column(Boolean, default=True)
config_password_lower = Column(Boolean, default=True)
config_password_upper = Column(Boolean, default=True)
config_password_character = Column(Boolean, default=True)
config_password_special = Column(Boolean, default=True)
config_session = Column(Integer, default=1)
config_ratelimiter = Column(Boolean, default=True)

View File

@ -22,6 +22,7 @@ import random
import io
import mimetypes
import re
import regex
import shutil
import socket
from datetime import datetime, timedelta
@ -54,7 +55,8 @@ from . import calibre_db, cli_param
from .tasks.convert import TaskConvert
from . import logger, config, db, ub, fs
from . import gdriveutils as gd
from .constants import STATIC_DIR as _STATIC_DIR, CACHE_TYPE_THUMBNAILS, THUMBNAIL_TYPE_COVER, THUMBNAIL_TYPE_SERIES, SUPPORTED_CALIBRE_BINARIES
from .constants import (STATIC_DIR as _STATIC_DIR, CACHE_TYPE_THUMBNAILS, THUMBNAIL_TYPE_COVER, THUMBNAIL_TYPE_SERIES,
SUPPORTED_CALIBRE_BINARIES)
from .subproc_wrapper import process_wait
from .services.worker import WorkerThread
from .tasks.mail import TaskEmail
@ -694,14 +696,16 @@ def valid_password(check_password):
if config.config_password_min_length > 0:
verify += r"^(?=.{" + str(config.config_password_min_length) + ",}$)"
if config.config_password_number:
verify += r"(?=.*?\d)"
verify += "(?=.*?\d)"
if config.config_password_lower:
verify += r"(?=.*?[a-z])"
verify += "(?=.*?[\p{Ll}])"
if config.config_password_upper:
verify += r"(?=.*?[A-Z])"
verify += "(?=.*?[\p{Lu}])"
if config.config_password_character:
verify += "(?=.*?[\p{Letter}])"
if config.config_password_special:
verify += r"(?=.*?[^A-Za-z\s0-9])"
match = re.match(verify, check_password)
verify += "(?=.*?[^\p{Letter}\s0-9])"
match = regex.match(verify, check_password)
if not match:
raise Exception(_("Password doesn't comply with password validation rules"))
return check_password

View File

@ -9,6 +9,7 @@
"wordSequences": "Das Passwort enthält Buchstabensequenzen",
"wordLowercase": "Bitte mindestens einen Kleinbuchstaben verwenden",
"wordUppercase": "Bitte mindestens einen Großbuchstaben verwenden",
"word": "Bitte mindestens einen Buchstaben verwenden",
"wordOneNumber": "Bitte mindestens eine Ziffern verwenden",
"wordOneSpecialChar": "Bitte mindestens ein Sonderzeichen verwenden",
"errorList": "Fehler:",

View File

@ -8,6 +8,7 @@
"wordRepetitions": "Too many repetitions",
"wordSequences": "Your password contains sequences",
"wordLowercase": "Use at least one lowercase character",
"word": "Use at least one character",
"wordUppercase": "Use at least one uppercase character",
"wordOneNumber": "Use at least one number",
"wordOneSpecialChar": "Use at least one special character",

View File

@ -144,13 +144,13 @@ try {
validation.wordTwoCharacterClasses = function(options, word, score) {
var specialCharRE = new RegExp(
'(.' + options.rules.specialCharClass + ')'
'(.' + options.rules.specialCharClass + ')', 'u'
);
if (
word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
(word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
(word.match(specialCharRE) && word.match(/[a-zA-Z0-9_]/))
word.match(/(\p{Ll}.*\p{Lu})|(\p{Lu}.*\p{Ll})/u) ||
(word.match(/(\p{Letter})/u) && word.match(/([0-9])/)) ||
(word.match(specialCharRE) && word.match(/[\p{Letter}0-9_]/u))
) {
return score;
}
@ -202,11 +202,15 @@ try {
};
validation.wordLowercase = function(options, word, score) {
return word.match(/[a-z]/) && score;
return word.match(/\p{Ll}/u) && score;
};
validation.wordUppercase = function(options, word, score) {
return word.match(/[A-Z]/) && score;
return word.match(/\p{Lu}/u) && score;
};
validation.word = function(options, word, score) {
return word.match(/\p{Letter}/u) && score;
};
validation.wordOneNumber = function(options, word, score) {
@ -218,7 +222,7 @@ try {
};
validation.wordOneSpecialChar = function(options, word, score) {
var specialCharRE = new RegExp(options.rules.specialCharClass);
var specialCharRE = new RegExp(options.rules.specialCharClass, 'u');
return word.match(specialCharRE) && score;
};
@ -228,27 +232,27 @@ try {
options.rules.specialCharClass +
'.*' +
options.rules.specialCharClass +
')'
')', 'u'
);
return word.match(twoSpecialCharRE) && score;
};
validation.wordUpperLowerCombo = function(options, word, score) {
return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
return word.match(/(\p{Ll}.*\p{Lu})|(\p{Lu}.*\p{Ll})/u) && score;
};
validation.wordLetterNumberCombo = function(options, word, score) {
return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
return word.match(/([\p{Letter}])/u) && word.match(/([0-9])/) && score;
};
validation.wordLetterNumberCharCombo = function(options, word, score) {
var letterNumberCharComboRE = new RegExp(
'([a-zA-Z0-9].*' +
'([\p{Letter}0-9].*' +
options.rules.specialCharClass +
')|(' +
options.rules.specialCharClass +
'.*[a-zA-Z0-9])'
'.*[\p{Letter}0-9])', 'u'
);
return word.match(letterNumberCharComboRE) && score;
@ -341,6 +345,7 @@ defaultOptions.rules.scores = {
wordTwoCharacterClasses: 2,
wordRepetitions: -25,
wordLowercase: 1,
word: 1,
wordUppercase: 3,
wordOneNumber: 3,
wordThreeNumbers: 5,
@ -361,6 +366,7 @@ defaultOptions.rules.activated = {
wordTwoCharacterClasses: true,
wordRepetitions: true,
wordLowercase: true,
word: true,
wordUppercase: true,
wordOneNumber: true,
wordThreeNumbers: true,
@ -372,7 +378,7 @@ defaultOptions.rules.activated = {
wordIsACommonPassword: true
};
defaultOptions.rules.raisePower = 1.4;
defaultOptions.rules.specialCharClass = "(?=.*?[^A-Za-z\s0-9])"; //'[!,@,#,$,%,^,&,*,?,_,~]';
defaultOptions.rules.specialCharClass = "(?=.*?[^\\p{Letter}\\s0-9])"; //'[!,@,#,$,%,^,&,*,?,_,~]';
// List taken from https://github.com/danielmiessler/SecLists (MIT License)
defaultOptions.rules.commonPasswords = [
'123456',

File diff suppressed because one or more lines are too long

View File

@ -38,22 +38,20 @@ $(document).ready(function() {
showVerdicts: false,
}
options.rules= {
specialCharClass: "(?=.*?[^A-Za-z\\s0-9])",
specialCharClass: "(?=.*?[^\\p{Letter}\\s0-9])",
activated: {
wordNotEmail: false,
wordMinLength: $('#password').data("min"),
// wordMaxLength: false,
// wordInvalidChar: true,
wordSimilarToUsername: false,
wordSequences: false,
wordTwoCharacterClasses: false,
wordRepetitions: false,
wordLowercase: $('#password').data("lower") === "True" ? true : false,
wordUppercase: $('#password').data("upper") === "True" ? true : false,
word: $('#password').data("word") === "True" ? true : false,
wordOneNumber: $('#password').data("number") === "True" ? true : false,
wordThreeNumbers: false,
wordOneSpecialChar: $('#password').data("special") === "True" ? true : false,
// wordTwoSpecialChar: true,
wordUpperLowerCombo: false,
wordLetterNumberCombo: false,
wordLetterNumberCharCombo: false

View File

@ -410,6 +410,10 @@
<input type="checkbox" id="config_password_upper" name="config_password_upper" {% if config.config_password_upper %}checked{% endif %}>
<label for="config_password_upper">{{_('Enforce uppercase characters')}}</label>
</div>
<div class="form-group" style="margin-left:10px;">
<input type="checkbox" id="config_password_character" name="config_password_character" {% if config.config_password_character %}checked{% endif %}>
<label for="config_password_lower">{{_('Enforce characters (needed For Chinese/Japanese/Korean Characters)')}}</label>
</div>
<div class="form-group" style="margin-left:10px;">
<input type="checkbox" id="config_password_special" name="config_password_special" {% if config.config_password_special %}checked{% endif %}>
<label for="config_password_special">{{_('Enforce special characters')}}</label>

View File

@ -21,7 +21,7 @@
{% endif %}
<div class="form-group">
<label for="password">{{_('Password')}}</label>
<input type="password" class="form-control" name="password" id="password" data-lang="{{ current_user.locale }}" data-verify="{{ config.config_password_policy }}" {% if config.config_password_policy %} data-min={{ config.config_password_min_length }} data-special={{ config.config_password_special }} data-upper={{ config.config_password_upper }} data-lower={{ config.config_password_lower }} data-number={{ config.config_password_number }}{% endif %} value="" autocomplete="off">
<input type="password" class="form-control" name="password" id="password" data-lang="{{ current_user.locale }}" data-verify="{{ config.config_password_policy }}" {% if config.config_password_policy %} data-min={{ config.config_password_min_length }} data-word={{ config.config_password_character }} data-special={{ config.config_password_special }} data-upper={{ config.config_password_upper }} data-lower={{ config.config_password_lower }} data-number={{ config.config_password_number }}{% endif %} value="" autocomplete="off">
</div>
{% endif %}
<div class="form-group">
@ -177,7 +177,7 @@
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/pwstrength/i18next.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/pwstrength/i18nextHttpBackend.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/pwstrength/pwstrength-bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/pwstrength/pwstrength-bootstrap.js') }}"></script>
<script src="{{ url_for('static', filename='js/password.js') }}"></script>
<script src="{{ url_for('static', filename='js/table.js') }}"></script>
{% endblock %}

View File

@ -18,3 +18,4 @@ flask-wtf>=0.14.2,<1.3.0
chardet>=3.0.0,<4.1.0
advocate>=1.0.0,<1.1.0
Flask-Limiter>=2.3.0,<3.6.0
regex>=2022.3.2,<2024.2.25