Merge branch 'Develop'
This commit is contained in:
commit
942bcff5c4
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -34,3 +34,4 @@ settings.yaml
|
||||||
gdrive_credentials
|
gdrive_credentials
|
||||||
client_secrets.json
|
client_secrets.json
|
||||||
gmail.json
|
gmail.json
|
||||||
|
/.key
|
||||||
|
|
2
cps.py
2
cps.py
|
@ -21,7 +21,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
# Add local path to sys.path so we can import cps
|
# Add local path to sys.path, so we can import cps
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
path = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, path)
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,11 @@ from . import config_sql
|
||||||
from . import cache_buster
|
from . import cache_buster
|
||||||
from . import ub, db
|
from . import ub, db
|
||||||
|
|
||||||
|
try:
|
||||||
|
from flask_limiter import Limiter
|
||||||
|
limiter_present = True
|
||||||
|
except ImportError:
|
||||||
|
limiter_present = False
|
||||||
try:
|
try:
|
||||||
from flask_wtf.csrf import CSRFProtect
|
from flask_wtf.csrf import CSRFProtect
|
||||||
wtf_present = True
|
wtf_present = True
|
||||||
|
@ -81,7 +86,7 @@ app.config.update(
|
||||||
|
|
||||||
lm = MyLoginManager()
|
lm = MyLoginManager()
|
||||||
|
|
||||||
config = config_sql._ConfigSQL()
|
config = config_sql.ConfigSQL()
|
||||||
|
|
||||||
cli_param = CliParameter()
|
cli_param = CliParameter()
|
||||||
|
|
||||||
|
@ -96,33 +101,36 @@ web_server = WebServer()
|
||||||
|
|
||||||
updater_thread = Updater()
|
updater_thread = Updater()
|
||||||
|
|
||||||
|
if limiter_present:
|
||||||
|
limiter = Limiter(key_func=True, headers_enabled=True, auto_check=False, swallow_errors=True)
|
||||||
|
else:
|
||||||
|
limiter = None
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
lm.login_view = 'web.login'
|
|
||||||
lm.anonymous_user = ub.Anonymous
|
|
||||||
lm.session_protection = 'strong'
|
|
||||||
|
|
||||||
if csrf:
|
if csrf:
|
||||||
csrf.init_app(app)
|
csrf.init_app(app)
|
||||||
|
|
||||||
cli_param.init()
|
cli_param.init()
|
||||||
|
|
||||||
ub.init_db(cli_param.settings_path, cli_param.user_credentials)
|
ub.init_db(cli_param.settings_path)
|
||||||
|
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
config_sql.load_configuration(config, ub.session, cli_param)
|
encrypt_key, error = config_sql.get_encryption_key(os.path.dirname(cli_param.settings_path))
|
||||||
|
|
||||||
db.CalibreDB.update_config(config)
|
config_sql.load_configuration(ub.session, encrypt_key)
|
||||||
db.CalibreDB.setup_db(config.config_calibre_dir, cli_param.settings_path)
|
config.init_config(ub.session, encrypt_key, cli_param)
|
||||||
calibre_db.init_db()
|
|
||||||
|
|
||||||
updater_thread.init_updater(config, web_server)
|
if error:
|
||||||
# Perform dry run of updater and exit afterwards
|
log.error(error)
|
||||||
if cli_param.dry_run:
|
|
||||||
updater_thread.dry_run()
|
|
||||||
sys.exit(0)
|
|
||||||
updater_thread.start()
|
|
||||||
|
|
||||||
|
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):
|
if sys.version_info < (3, 0):
|
||||||
log.info(
|
log.info(
|
||||||
'*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, '
|
'*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, '
|
||||||
|
@ -139,6 +147,22 @@ def create_app():
|
||||||
'Please install it using pip: "pip install flask-WTF" ***')
|
'Please install it using pip: "pip install flask-WTF" ***')
|
||||||
web_server.stop(True)
|
web_server.stop(True)
|
||||||
sys.exit(7)
|
sys.exit(7)
|
||||||
|
|
||||||
|
lm.login_view = 'web.login'
|
||||||
|
lm.anonymous_user = ub.Anonymous
|
||||||
|
lm.session_protection = 'strong' if config.config_session == 1 else "basic"
|
||||||
|
|
||||||
|
db.CalibreDB.update_config(config)
|
||||||
|
db.CalibreDB.setup_db(config.config_calibre_dir, cli_param.settings_path)
|
||||||
|
calibre_db.init_db()
|
||||||
|
|
||||||
|
updater_thread.init_updater(config, web_server)
|
||||||
|
# Perform dry run of updater and exit afterwards
|
||||||
|
if cli_param.dry_run:
|
||||||
|
updater_thread.dry_run()
|
||||||
|
sys.exit(0)
|
||||||
|
updater_thread.start()
|
||||||
|
|
||||||
for res in dependency_check() + dependency_check(True):
|
for res in dependency_check() + dependency_check(True):
|
||||||
log.info('*** "{}" version does not meet the requirements. '
|
log.info('*** "{}" version does not meet the requirements. '
|
||||||
'Should: {}, Found: {}, please consider installing required version ***'
|
'Should: {}, Found: {}, please consider installing required version ***'
|
||||||
|
@ -150,7 +174,6 @@ def create_app():
|
||||||
if os.environ.get('FLASK_DEBUG'):
|
if os.environ.get('FLASK_DEBUG'):
|
||||||
cache_buster.init_cache_busting(app)
|
cache_buster.init_cache_busting(app)
|
||||||
log.info('Starting Calibre Web...')
|
log.info('Starting Calibre Web...')
|
||||||
|
|
||||||
Principal(app)
|
Principal(app)
|
||||||
lm.init_app(app)
|
lm.init_app(app)
|
||||||
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
||||||
|
@ -168,9 +191,13 @@ def create_app():
|
||||||
services.ldap.init_app(app, config)
|
services.ldap.init_app(app, config)
|
||||||
if services.goodreads_support:
|
if services.goodreads_support:
|
||||||
services.goodreads_support.connect(config.config_goodreads_api_key,
|
services.goodreads_support.connect(config.config_goodreads_api_key,
|
||||||
config.config_goodreads_api_secret,
|
config.config_goodreads_api_secret_e,
|
||||||
config.config_use_goodreads)
|
config.config_use_goodreads)
|
||||||
config.store_calibre_uuid(calibre_db, db.Library_Id)
|
config.store_calibre_uuid(calibre_db, db.Library_Id)
|
||||||
|
# Configure rate limiter
|
||||||
|
app.config.update(RATELIMIT_ENABLED=config.config_ratelimiter)
|
||||||
|
limiter.init_app(app)
|
||||||
|
|
||||||
# Register scheduled tasks
|
# Register scheduled tasks
|
||||||
from .schedule import register_scheduled_tasks, register_startup_tasks
|
from .schedule import register_scheduled_tasks, register_startup_tasks
|
||||||
register_scheduled_tasks(config.schedule_reconnect)
|
register_scheduled_tasks(config.schedule_reconnect)
|
||||||
|
|
57
cps/admin.py
57
cps/admin.py
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import base64
|
|
||||||
import json
|
import json
|
||||||
import operator
|
import operator
|
||||||
import time
|
import time
|
||||||
|
@ -104,7 +103,6 @@ def before_request():
|
||||||
if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path:
|
if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path:
|
||||||
logout_user()
|
logout_user()
|
||||||
g.constants = constants
|
g.constants = constants
|
||||||
# g.user = current_user
|
|
||||||
g.google_site_verification = os.getenv('GOOGLE_SITE_VERIFICATION','')
|
g.google_site_verification = os.getenv('GOOGLE_SITE_VERIFICATION','')
|
||||||
g.allow_registration = config.config_public_reg
|
g.allow_registration = config.config_public_reg
|
||||||
g.allow_anonymous = config.config_anonbrowse
|
g.allow_anonymous = config.config_anonbrowse
|
||||||
|
@ -116,6 +114,7 @@ def before_request():
|
||||||
'admin.simulatedbchange',
|
'admin.simulatedbchange',
|
||||||
'admin.db_configuration',
|
'admin.db_configuration',
|
||||||
'web.login',
|
'web.login',
|
||||||
|
'web.login_post',
|
||||||
'web.logout',
|
'web.logout',
|
||||||
'admin.load_dialogtexts',
|
'admin.load_dialogtexts',
|
||||||
'admin.ajax_pathchooser'):
|
'admin.ajax_pathchooser'):
|
||||||
|
@ -214,12 +213,12 @@ def admin():
|
||||||
commit = version['version']
|
commit = version['version']
|
||||||
|
|
||||||
all_user = ub.session.query(ub.User).all()
|
all_user = ub.session.query(ub.User).all()
|
||||||
email_settings = config.get_mail_settings()
|
# email_settings = mail_config.get_mail_settings()
|
||||||
schedule_time = format_time(datetime_time(hour=config.schedule_start_time), format="short")
|
schedule_time = format_time(datetime_time(hour=config.schedule_start_time), format="short")
|
||||||
t = timedelta(hours=config.schedule_duration // 60, minutes=config.schedule_duration % 60)
|
t = timedelta(hours=config.schedule_duration // 60, minutes=config.schedule_duration % 60)
|
||||||
schedule_duration = format_timedelta(t, threshold=.99)
|
schedule_duration = format_timedelta(t, threshold=.99)
|
||||||
|
|
||||||
return render_title_template("admin.html", allUser=all_user, email=email_settings, config=config, commit=commit,
|
return render_title_template("admin.html", allUser=all_user, config=config, commit=commit,
|
||||||
feature_support=feature_support, schedule_time=schedule_time,
|
feature_support=feature_support, schedule_time=schedule_time,
|
||||||
schedule_duration=schedule_duration,
|
schedule_duration=schedule_duration,
|
||||||
title=_("Admin page"), page="admin")
|
title=_("Admin page"), page="admin")
|
||||||
|
@ -1084,7 +1083,7 @@ def _config_checkbox_int(to_save, x):
|
||||||
|
|
||||||
|
|
||||||
def _config_string(to_save, x):
|
def _config_string(to_save, x):
|
||||||
return config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
|
return config.set_from_dictionary(to_save, x, lambda y: y.strip().strip(u'\u200B\u200C\u200D\ufeff') if y else y)
|
||||||
|
|
||||||
|
|
||||||
def _configuration_gdrive_helper(to_save):
|
def _configuration_gdrive_helper(to_save):
|
||||||
|
@ -1173,9 +1172,9 @@ def _configuration_ldap_helper(to_save):
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_cert_path")
|
reboot_required |= _config_string(to_save, "config_ldap_cert_path")
|
||||||
reboot_required |= _config_string(to_save, "config_ldap_key_path")
|
reboot_required |= _config_string(to_save, "config_ldap_key_path")
|
||||||
_config_string(to_save, "config_ldap_group_name")
|
_config_string(to_save, "config_ldap_group_name")
|
||||||
if to_save.get("config_ldap_serv_password", "") != "":
|
if to_save.get("config_ldap_serv_password_e", "") != "":
|
||||||
reboot_required |= 1
|
reboot_required |= 1
|
||||||
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8')
|
config.set_from_dictionary(to_save, "config_ldap_serv_password_e")
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
if not config.config_ldap_provider_url \
|
if not config.config_ldap_provider_url \
|
||||||
|
@ -1187,7 +1186,7 @@ def _configuration_ldap_helper(to_save):
|
||||||
|
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
||||||
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password):
|
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password_e):
|
||||||
return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account and Password'))
|
return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account and Password'))
|
||||||
else:
|
else:
|
||||||
if not config.config_ldap_serv_username:
|
if not config.config_ldap_serv_username:
|
||||||
|
@ -1255,7 +1254,7 @@ def new_user():
|
||||||
kobo_support=kobo_support, registered_oauth=oauth_check)
|
kobo_support=kobo_support, registered_oauth=oauth_check)
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/admin/mailsettings")
|
@admi.route("/admin/mailsettings", methods=["GET"])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def edit_mailsettings():
|
def edit_mailsettings():
|
||||||
|
@ -1288,7 +1287,7 @@ def update_mailsettings():
|
||||||
else:
|
else:
|
||||||
_config_int(to_save, "mail_port")
|
_config_int(to_save, "mail_port")
|
||||||
_config_int(to_save, "mail_use_ssl")
|
_config_int(to_save, "mail_use_ssl")
|
||||||
_config_string(to_save, "mail_password")
|
_config_string(to_save, "mail_password_e")
|
||||||
_config_int(to_save, "mail_size", lambda y: int(y) * 1024 * 1024)
|
_config_int(to_save, "mail_size", lambda y: int(y) * 1024 * 1024)
|
||||||
config.mail_server = to_save.get('mail_server', "").strip()
|
config.mail_server = to_save.get('mail_server', "").strip()
|
||||||
config.mail_from = to_save.get('mail_from', "").strip()
|
config.mail_from = to_save.get('mail_from', "").strip()
|
||||||
|
@ -1771,10 +1770,10 @@ def _configuration_update_helper():
|
||||||
# Goodreads configuration
|
# Goodreads configuration
|
||||||
_config_checkbox(to_save, "config_use_goodreads")
|
_config_checkbox(to_save, "config_use_goodreads")
|
||||||
_config_string(to_save, "config_goodreads_api_key")
|
_config_string(to_save, "config_goodreads_api_key")
|
||||||
_config_string(to_save, "config_goodreads_api_secret")
|
_config_string(to_save, "config_goodreads_api_secret_e")
|
||||||
if services.goodreads_support:
|
if services.goodreads_support:
|
||||||
services.goodreads_support.connect(config.config_goodreads_api_key,
|
services.goodreads_support.connect(config.config_goodreads_api_key,
|
||||||
config.config_goodreads_api_secret,
|
config.config_goodreads_api_secret_e,
|
||||||
config.config_use_goodreads)
|
config.config_use_goodreads)
|
||||||
|
|
||||||
_config_int(to_save, "config_updatechannel")
|
_config_int(to_save, "config_updatechannel")
|
||||||
|
@ -1787,10 +1786,25 @@ def _configuration_update_helper():
|
||||||
if config.config_login_type == constants.LOGIN_OAUTH:
|
if config.config_login_type == constants.LOGIN_OAUTH:
|
||||||
reboot_required |= _configuration_oauth_helper(to_save)
|
reboot_required |= _configuration_oauth_helper(to_save)
|
||||||
|
|
||||||
|
# logfile configuration
|
||||||
reboot, message = _configuration_logfile_helper(to_save)
|
reboot, message = _configuration_logfile_helper(to_save)
|
||||||
if message:
|
if message:
|
||||||
return message
|
return message
|
||||||
reboot_required |= reboot
|
reboot_required |= reboot
|
||||||
|
|
||||||
|
# security configuration
|
||||||
|
_config_checkbox(to_save, "config_password_policy")
|
||||||
|
_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_special")
|
||||||
|
if 0 < int(to_save.get("config_password_min_length", "0")) < 41:
|
||||||
|
_config_int(to_save, "config_password_min_length")
|
||||||
|
else:
|
||||||
|
return _configuration_result(_('Password length has to be between 1 and 40'))
|
||||||
|
reboot_required |= _config_int(to_save, "config_session")
|
||||||
|
reboot_required |= _config_checkbox(to_save, "config_ratelimiter")
|
||||||
|
|
||||||
# Rarfile Content configuration
|
# Rarfile Content configuration
|
||||||
_config_string(to_save, "config_rarfile_location")
|
_config_string(to_save, "config_rarfile_location")
|
||||||
if "config_rarfile_location" in to_save:
|
if "config_rarfile_location" in to_save:
|
||||||
|
@ -1859,11 +1873,11 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support):
|
||||||
content.sidebar_view |= constants.DETAIL_RANDOM
|
content.sidebar_view |= constants.DETAIL_RANDOM
|
||||||
|
|
||||||
content.role = constants.selected_roles(to_save)
|
content.role = constants.selected_roles(to_save)
|
||||||
content.password = generate_password_hash(to_save["password"])
|
|
||||||
try:
|
try:
|
||||||
if not to_save["name"] or not to_save["email"] or not to_save["password"]:
|
if not to_save["name"] or not to_save["email"] or not to_save["password"]:
|
||||||
log.info("Missing entries on new user")
|
log.info("Missing entries on new user")
|
||||||
raise Exception(_("Oops! Please complete all fields."))
|
raise Exception(_("Oops! Please complete all fields."))
|
||||||
|
content.password = generate_password_hash(helper.valid_password(to_save.get("password", "")))
|
||||||
content.email = check_email(to_save["email"])
|
content.email = check_email(to_save["email"])
|
||||||
# Query username, if not existing, change
|
# Query username, if not existing, change
|
||||||
content.name = check_username(to_save["name"])
|
content.name = check_username(to_save["name"])
|
||||||
|
@ -1947,14 +1961,6 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
||||||
log.warning("No admin user remaining, can't remove admin role from {}".format(content.name))
|
log.warning("No admin user remaining, can't remove admin role from {}".format(content.name))
|
||||||
flash(_("No admin user remaining, can't remove admin role"), category="error")
|
flash(_("No admin user remaining, can't remove admin role"), category="error")
|
||||||
return redirect(url_for('admin.admin'))
|
return redirect(url_for('admin.admin'))
|
||||||
if to_save.get("password"):
|
|
||||||
content.password = generate_password_hash(to_save["password"])
|
|
||||||
anonymous = content.is_anonymous
|
|
||||||
content.role = constants.selected_roles(to_save)
|
|
||||||
if anonymous:
|
|
||||||
content.role |= constants.ROLE_ANONYMOUS
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_ANONYMOUS
|
|
||||||
|
|
||||||
val = [int(k[5:]) for k in to_save if k.startswith('show_')]
|
val = [int(k[5:]) for k in to_save if k.startswith('show_')]
|
||||||
sidebar, __ = get_sidebar_config()
|
sidebar, __ = get_sidebar_config()
|
||||||
|
@ -1982,6 +1988,15 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
|
||||||
if to_save.get("locale"):
|
if to_save.get("locale"):
|
||||||
content.locale = to_save["locale"]
|
content.locale = to_save["locale"]
|
||||||
try:
|
try:
|
||||||
|
anonymous = content.is_anonymous
|
||||||
|
content.role = constants.selected_roles(to_save)
|
||||||
|
if anonymous:
|
||||||
|
content.role |= constants.ROLE_ANONYMOUS
|
||||||
|
else:
|
||||||
|
content.role &= ~constants.ROLE_ANONYMOUS
|
||||||
|
if to_save.get("password", ""):
|
||||||
|
content.password = generate_password_hash(helper.valid_password(to_save.get["password"]))
|
||||||
|
|
||||||
new_email = valid_email(to_save.get("email", content.email))
|
new_email = valid_email(to_save.get("email", content.email))
|
||||||
if not new_email:
|
if not new_email:
|
||||||
raise Exception(_("Email can't be empty and has to be a valid Email"))
|
raise Exception(_("Email can't be empty and has to be a valid Email"))
|
||||||
|
|
|
@ -23,6 +23,10 @@ import json
|
||||||
from sqlalchemy import Column, String, Integer, SmallInteger, Boolean, BLOB, JSON
|
from sqlalchemy import Column, String, Integer, SmallInteger, Boolean, BLOB, JSON
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError
|
||||||
from sqlalchemy.sql.expression import text
|
from sqlalchemy.sql.expression import text
|
||||||
|
from sqlalchemy import exists
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
import cryptography.exceptions
|
||||||
|
from base64 import urlsafe_b64decode
|
||||||
try:
|
try:
|
||||||
# Compatibility with sqlalchemy 2.0
|
# Compatibility with sqlalchemy 2.0
|
||||||
from sqlalchemy.orm import declarative_base
|
from sqlalchemy.orm import declarative_base
|
||||||
|
@ -56,7 +60,8 @@ class _Settings(_Base):
|
||||||
mail_port = Column(Integer, default=25)
|
mail_port = Column(Integer, default=25)
|
||||||
mail_use_ssl = Column(SmallInteger, default=0)
|
mail_use_ssl = Column(SmallInteger, default=0)
|
||||||
mail_login = Column(String, default='mail@example.com')
|
mail_login = Column(String, default='mail@example.com')
|
||||||
mail_password = Column(String, default='mypassword')
|
mail_password_e = Column(String)
|
||||||
|
mail_password = Column(String)
|
||||||
mail_from = Column(String, default='automailer <mail@example.com>')
|
mail_from = Column(String, default='automailer <mail@example.com>')
|
||||||
mail_size = Column(Integer, default=25*1024*1024)
|
mail_size = Column(Integer, default=25*1024*1024)
|
||||||
mail_server_type = Column(SmallInteger, default=0)
|
mail_server_type = Column(SmallInteger, default=0)
|
||||||
|
@ -75,7 +80,6 @@ class _Settings(_Base):
|
||||||
config_authors_max = Column(Integer, default=0)
|
config_authors_max = Column(Integer, default=0)
|
||||||
config_read_column = Column(Integer, default=0)
|
config_read_column = Column(Integer, default=0)
|
||||||
config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+')
|
config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+')
|
||||||
# config_mature_content_tags = Column(String, default='')
|
|
||||||
config_theme = Column(Integer, default=0)
|
config_theme = Column(Integer, default=0)
|
||||||
|
|
||||||
config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
|
config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
|
||||||
|
@ -107,6 +111,7 @@ class _Settings(_Base):
|
||||||
|
|
||||||
config_use_goodreads = Column(Boolean, default=False)
|
config_use_goodreads = Column(Boolean, default=False)
|
||||||
config_goodreads_api_key = Column(String)
|
config_goodreads_api_key = Column(String)
|
||||||
|
config_goodreads_api_secret_e = Column(String)
|
||||||
config_goodreads_api_secret = Column(String)
|
config_goodreads_api_secret = Column(String)
|
||||||
config_register_email = Column(Boolean, default=False)
|
config_register_email = Column(Boolean, default=False)
|
||||||
config_login_type = Column(Integer, default=0)
|
config_login_type = Column(Integer, default=0)
|
||||||
|
@ -117,7 +122,8 @@ class _Settings(_Base):
|
||||||
config_ldap_port = Column(SmallInteger, default=389)
|
config_ldap_port = Column(SmallInteger, default=389)
|
||||||
config_ldap_authentication = Column(SmallInteger, default=constants.LDAP_AUTH_SIMPLE)
|
config_ldap_authentication = Column(SmallInteger, default=constants.LDAP_AUTH_SIMPLE)
|
||||||
config_ldap_serv_username = Column(String, default='cn=admin,dc=example,dc=org')
|
config_ldap_serv_username = Column(String, default='cn=admin,dc=example,dc=org')
|
||||||
config_ldap_serv_password = Column(String, default="")
|
config_ldap_serv_password_e = Column(String)
|
||||||
|
config_ldap_serv_password = Column(String)
|
||||||
config_ldap_encryption = Column(SmallInteger, default=0)
|
config_ldap_encryption = Column(SmallInteger, default=0)
|
||||||
config_ldap_cacert_path = Column(String, default="")
|
config_ldap_cacert_path = Column(String, default="")
|
||||||
config_ldap_cert_path = Column(String, default="")
|
config_ldap_cert_path = Column(String, default="")
|
||||||
|
@ -148,23 +154,33 @@ class _Settings(_Base):
|
||||||
schedule_generate_series_covers = Column(Boolean, default=False)
|
schedule_generate_series_covers = Column(Boolean, default=False)
|
||||||
schedule_reconnect = Column(Boolean, default=False)
|
schedule_reconnect = Column(Boolean, default=False)
|
||||||
|
|
||||||
|
config_password_policy = Column(Boolean, default=True)
|
||||||
|
config_password_min_length = Column(Integer, default=8)
|
||||||
|
config_password_number = Column(Boolean, default=True)
|
||||||
|
config_password_lower = Column(Boolean, default=True)
|
||||||
|
config_password_upper = Column(Boolean, default=True)
|
||||||
|
config_password_special = Column(Boolean, default=True)
|
||||||
|
config_session = Column(Integer, default=1)
|
||||||
|
config_ratelimiter = Column(Boolean, default=True)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
# Class holds all application specific settings in calibre-web
|
# Class holds all application specific settings in calibre-web
|
||||||
class _ConfigSQL(object):
|
class ConfigSQL(object):
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
self.__dict__["dirty"] = list()
|
||||||
|
|
||||||
def init_config(self, session, cli):
|
def init_config(self, session, secret_key, cli):
|
||||||
self._session = session
|
self._session = session
|
||||||
self._settings = None
|
self._settings = None
|
||||||
self.db_configured = None
|
self.db_configured = None
|
||||||
self.config_calibre_dir = None
|
self.config_calibre_dir = None
|
||||||
self.load()
|
self._fernet = Fernet(secret_key)
|
||||||
self.cli = cli
|
self.cli = cli
|
||||||
|
self.load()
|
||||||
|
|
||||||
change = False
|
change = False
|
||||||
if self.config_converterpath == None: # pylint: disable=access-member-before-definition
|
if self.config_converterpath == None: # pylint: disable=access-member-before-definition
|
||||||
|
@ -293,10 +309,10 @@ class _ConfigSQL(object):
|
||||||
setattr(self, field, new_value)
|
setattr(self, field, new_value)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def toDict(self):
|
def to_dict(self):
|
||||||
storage = {}
|
storage = {}
|
||||||
for k, v in self.__dict__.items():
|
for k, v in self.__dict__.items():
|
||||||
if k[0] != '_' and not k.endswith("password") and not k.endswith("secret") and not k == "cli":
|
if k[0] != '_' and not k.endswith("_e") and not k == "cli":
|
||||||
storage[k] = v
|
storage[k] = v
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
|
@ -310,6 +326,12 @@ class _ConfigSQL(object):
|
||||||
column = s.__class__.__dict__.get(k)
|
column = s.__class__.__dict__.get(k)
|
||||||
if column.default is not None:
|
if column.default is not None:
|
||||||
v = column.default.arg
|
v = column.default.arg
|
||||||
|
if k.endswith("_e") and v is not None:
|
||||||
|
try:
|
||||||
|
setattr(self, k, self._fernet.decrypt(v).decode())
|
||||||
|
except cryptography.fernet.InvalidToken:
|
||||||
|
setattr(self, k, "")
|
||||||
|
else:
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
have_metadata_db = bool(self.config_calibre_dir)
|
have_metadata_db = bool(self.config_calibre_dir)
|
||||||
|
@ -332,16 +354,20 @@ class _ConfigSQL(object):
|
||||||
except OperationalError as e:
|
except OperationalError as e:
|
||||||
log.error('Database error: %s', e)
|
log.error('Database error: %s', e)
|
||||||
self._session.rollback()
|
self._session.rollback()
|
||||||
|
self.__dict__["dirty"] = list()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Apply all configuration values to the underlying storage."""
|
"""Apply all configuration values to the underlying storage."""
|
||||||
s = self._read_from_storage() # type: _Settings
|
s = self._read_from_storage() # type: _Settings
|
||||||
|
|
||||||
for k, v in self.__dict__.items():
|
for k in self.dirty:
|
||||||
if k[0] == '_':
|
if k[0] == '_':
|
||||||
continue
|
continue
|
||||||
if hasattr(s, k):
|
if hasattr(s, k):
|
||||||
setattr(s, k, v)
|
if k.endswith("_e"):
|
||||||
|
setattr(s, k, self._fernet.encrypt(self.__dict__[k].encode()))
|
||||||
|
else:
|
||||||
|
setattr(s, k, self.__dict__[k])
|
||||||
|
|
||||||
log.debug("_ConfigSQL updating storage")
|
log.debug("_ConfigSQL updating storage")
|
||||||
self._session.merge(s)
|
self._session.merge(s)
|
||||||
|
@ -357,7 +383,6 @@ class _ConfigSQL(object):
|
||||||
log.error(error)
|
log.error(error)
|
||||||
log.warning("invalidating configuration")
|
log.warning("invalidating configuration")
|
||||||
self.db_configured = False
|
self.db_configured = False
|
||||||
# self.config_calibre_dir = None
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def store_calibre_uuid(self, calibre_db, Library_table):
|
def store_calibre_uuid(self, calibre_db, Library_table):
|
||||||
|
@ -369,8 +394,40 @@ class _ConfigSQL(object):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def __setattr__(self, attr_name, attr_value):
|
||||||
|
super().__setattr__(attr_name, attr_value)
|
||||||
|
self.__dict__["dirty"].append(attr_name)
|
||||||
|
|
||||||
def _migrate_table(session, orm_class):
|
|
||||||
|
def _encrypt_fields(session, secret_key):
|
||||||
|
try:
|
||||||
|
session.query(exists().where(_Settings.mail_password_e)).scalar()
|
||||||
|
except OperationalError:
|
||||||
|
with session.bind.connect() as conn:
|
||||||
|
conn.execute("ALTER TABLE settings ADD column 'mail_password_e' String")
|
||||||
|
conn.execute("ALTER TABLE settings ADD column 'config_goodreads_api_secret_e' String")
|
||||||
|
conn.execute("ALTER TABLE settings ADD column 'config_ldap_serv_password_e' String")
|
||||||
|
session.commit()
|
||||||
|
crypter = Fernet(secret_key)
|
||||||
|
settings = session.query(_Settings.mail_password, _Settings.config_goodreads_api_secret,
|
||||||
|
_Settings.config_ldap_serv_password).first()
|
||||||
|
if settings.mail_password:
|
||||||
|
session.query(_Settings).update(
|
||||||
|
{_Settings.mail_password_e: crypter.encrypt(settings.mail_password.encode())})
|
||||||
|
if settings.config_goodreads_api_secret:
|
||||||
|
session.query(_Settings).update(
|
||||||
|
{_Settings.config_goodreads_api_secret_e:
|
||||||
|
crypter.encrypt(settings.config_goodreads_api_secret.encode())})
|
||||||
|
if settings.config_ldap_serv_password:
|
||||||
|
session.query(_Settings).update(
|
||||||
|
{_Settings.config_ldap_serv_password_e:
|
||||||
|
crypter.encrypt(settings.config_ldap_serv_password.encode())})
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_table(session, orm_class, secret_key=None):
|
||||||
|
if secret_key:
|
||||||
|
_encrypt_fields(session, secret_key)
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
for column_name, column in orm_class.__dict__.items():
|
for column_name, column in orm_class.__dict__.items():
|
||||||
|
@ -446,22 +503,18 @@ def autodetect_kepubify_binary():
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _migrate_database(session):
|
def _migrate_database(session, secret_key):
|
||||||
# make sure the table is created, if it does not exist
|
# make sure the table is created, if it does not exist
|
||||||
_Base.metadata.create_all(session.bind)
|
_Base.metadata.create_all(session.bind)
|
||||||
_migrate_table(session, _Settings)
|
_migrate_table(session, _Settings, secret_key)
|
||||||
_migrate_table(session, _Flask_Settings)
|
_migrate_table(session, _Flask_Settings)
|
||||||
|
|
||||||
|
|
||||||
def load_configuration(conf, session, cli):
|
def load_configuration(session, secret_key):
|
||||||
_migrate_database(session)
|
_migrate_database(session, secret_key)
|
||||||
|
|
||||||
if not session.query(_Settings).count():
|
if not session.query(_Settings).count():
|
||||||
session.add(_Settings())
|
session.add(_Settings())
|
||||||
session.commit()
|
session.commit()
|
||||||
# conf = _ConfigSQL()
|
|
||||||
conf.init_config(session, cli)
|
|
||||||
# return conf
|
|
||||||
|
|
||||||
|
|
||||||
def get_flask_session_key(_session):
|
def get_flask_session_key(_session):
|
||||||
|
@ -471,3 +524,25 @@ def get_flask_session_key(_session):
|
||||||
_session.add(flask_settings)
|
_session.add(flask_settings)
|
||||||
_session.commit()
|
_session.commit()
|
||||||
return flask_settings.flask_session_key
|
return flask_settings.flask_session_key
|
||||||
|
|
||||||
|
|
||||||
|
def get_encryption_key(key_path):
|
||||||
|
key_file = os.path.join(key_path, ".key")
|
||||||
|
generate = True
|
||||||
|
error = ""
|
||||||
|
if os.path.exists(key_file) and os.path.getsize(key_file) > 32:
|
||||||
|
with open(key_file, "rb") as f:
|
||||||
|
key = f.read()
|
||||||
|
try:
|
||||||
|
urlsafe_b64decode(key)
|
||||||
|
generate = False
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if generate:
|
||||||
|
key = Fernet.generate_key()
|
||||||
|
try:
|
||||||
|
with open(key_file, "wb") as f:
|
||||||
|
f.write(key)
|
||||||
|
except PermissionError as e:
|
||||||
|
error = e
|
||||||
|
return key, error
|
||||||
|
|
|
@ -65,7 +65,7 @@ def send_debug():
|
||||||
file_list.remove(element)
|
file_list.remove(element)
|
||||||
memory_zip = BytesIO()
|
memory_zip = BytesIO()
|
||||||
with zipfile.ZipFile(memory_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
|
with zipfile.ZipFile(memory_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
|
||||||
zf.writestr('settings.txt', json.dumps(config.toDict(), sort_keys=True, indent=2))
|
zf.writestr('settings.txt', json.dumps(config.to_dict(), sort_keys=True, indent=2))
|
||||||
zf.writestr('libs.txt', json.dumps(collect_stats(), sort_keys=True, indent=2, cls=lazyEncoder))
|
zf.writestr('libs.txt', json.dumps(collect_stats(), sort_keys=True, indent=2, cls=lazyEncoder))
|
||||||
for fp in file_list:
|
for fp in file_list:
|
||||||
zf.write(fp, os.path.basename(fp))
|
zf.write(fp, os.path.basename(fp))
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import io
|
import io
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import re
|
import re
|
||||||
|
@ -612,7 +613,7 @@ def reset_password(user_id):
|
||||||
if not config.get_mail_server_configured():
|
if not config.get_mail_server_configured():
|
||||||
return 2, None
|
return 2, None
|
||||||
try:
|
try:
|
||||||
password = generate_random_password()
|
password = generate_random_password(config.config_password_min_length)
|
||||||
existing_user.password = generate_password_hash(password)
|
existing_user.password = generate_password_hash(password)
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
send_registration_mail(existing_user.email, existing_user.name, password, True)
|
send_registration_mail(existing_user.email, existing_user.name, password, True)
|
||||||
|
@ -621,11 +622,35 @@ def reset_password(user_id):
|
||||||
ub.session.rollback()
|
ub.session.rollback()
|
||||||
return 0, None
|
return 0, None
|
||||||
|
|
||||||
|
def generate_random_password(min_length):
|
||||||
|
min_length = max(8, min_length) - 4
|
||||||
|
random_source = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?"
|
||||||
|
# select 1 lowercase
|
||||||
|
s = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
password = [s[c % len(s)] for c in os.urandom(1)]
|
||||||
|
# select 1 uppercase
|
||||||
|
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
password.extend([s[c % len(s)] for c in os.urandom(1)])
|
||||||
|
# select 1 digit
|
||||||
|
s = "01234567890"
|
||||||
|
password.extend([s[c % len(s)] for c in os.urandom(1)])
|
||||||
|
# select 1 special symbol
|
||||||
|
s = "!@#$%&*()?"
|
||||||
|
password.extend([s[c % len(s)] for c in os.urandom(1)])
|
||||||
|
|
||||||
def generate_random_password():
|
# generate other characters
|
||||||
|
password.extend([random_source[c % len(random_source)] for c in os.urandom(min_length)])
|
||||||
|
|
||||||
|
# password_list = list(password)
|
||||||
|
# shuffle all characters
|
||||||
|
random.SystemRandom().shuffle(password)
|
||||||
|
return ''.join(password)
|
||||||
|
|
||||||
|
|
||||||
|
'''def generate_random_password(min_length):
|
||||||
s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?"
|
s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?"
|
||||||
passlen = 8
|
passlen = min_length
|
||||||
return "".join(s[c % len(s)] for c in os.urandom(passlen))
|
return "".join(s[c % len(s)] for c in os.urandom(passlen))'''
|
||||||
|
|
||||||
|
|
||||||
def uniq(inpt):
|
def uniq(inpt):
|
||||||
|
@ -664,6 +689,23 @@ def valid_email(email):
|
||||||
raise Exception(_("Invalid Email address format"))
|
raise Exception(_("Invalid Email address format"))
|
||||||
return email
|
return email
|
||||||
|
|
||||||
|
def valid_password(check_password):
|
||||||
|
if config.config_password_policy:
|
||||||
|
verify = ""
|
||||||
|
if config.config_password_min_length > 0:
|
||||||
|
verify += "^(?=.{" + str(config.config_password_min_length) + ",}$)"
|
||||||
|
if config.config_password_number:
|
||||||
|
verify += "(?=.*?\d)"
|
||||||
|
if config.config_password_lower:
|
||||||
|
verify += "(?=.*?[a-z])"
|
||||||
|
if config.config_password_upper:
|
||||||
|
verify += "(?=.*?[A-Z])"
|
||||||
|
if config.config_password_special:
|
||||||
|
verify += "(?=.*?[^A-Za-z\s0-9])"
|
||||||
|
match = re.match(verify, check_password)
|
||||||
|
if not match:
|
||||||
|
raise Exception(_("Password doesn't comply with password validation rules"))
|
||||||
|
return check_password
|
||||||
# ################################# External interface #################################
|
# ################################# External interface #################################
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,11 +64,12 @@ from datetime import datetime
|
||||||
from os import urandom
|
from os import urandom
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask import g, Blueprint, url_for, abort, request
|
from flask import g, Blueprint, abort, request
|
||||||
from flask_login import login_user, current_user, login_required
|
from flask_login import login_user, current_user, login_required
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
|
from flask_limiter import RateLimitExceeded
|
||||||
|
|
||||||
from . import logger, config, calibre_db, db, helper, ub, lm
|
from . import logger, config, calibre_db, db, helper, ub, lm, limiter
|
||||||
from .render_template import render_title_template
|
from .render_template import render_title_template
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
@ -151,6 +152,10 @@ def requires_kobo_auth(f):
|
||||||
def inner(*args, **kwargs):
|
def inner(*args, **kwargs):
|
||||||
auth_token = get_auth_token()
|
auth_token = get_auth_token()
|
||||||
if auth_token is not None:
|
if auth_token is not None:
|
||||||
|
try:
|
||||||
|
limiter.check()
|
||||||
|
except RateLimitExceeded:
|
||||||
|
return abort(429)
|
||||||
user = (
|
user = (
|
||||||
ub.session.query(ub.User)
|
ub.session.query(ub.User)
|
||||||
.join(ub.RemoteAuthToken)
|
.join(ub.RemoteAuthToken)
|
||||||
|
@ -159,6 +164,7 @@ def requires_kobo_auth(f):
|
||||||
)
|
)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
login_user(user)
|
login_user(user)
|
||||||
|
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
log.debug("Received Kobo request without a recognizable auth token.")
|
log.debug("Received Kobo request without a recognizable auth token.")
|
||||||
return abort(401)
|
return abort(401)
|
||||||
|
|
10
cps/main.py
10
cps/main.py
|
@ -18,9 +18,14 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from . import create_app
|
from . import create_app, limiter
|
||||||
from .jinjia import jinjia
|
from .jinjia import jinjia
|
||||||
from .remotelogin import remotelogin
|
from .remotelogin import remotelogin
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
|
||||||
|
def request_username():
|
||||||
|
return request.authorization.username
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
@ -39,6 +44,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
from .kobo import kobo, get_kobo_activated
|
from .kobo import kobo, get_kobo_activated
|
||||||
from .kobo_auth import kobo_auth
|
from .kobo_auth import kobo_auth
|
||||||
|
from flask_limiter.util import get_remote_address
|
||||||
kobo_available = get_kobo_activated()
|
kobo_available = get_kobo_activated()
|
||||||
except (ImportError, AttributeError): # Catch also error for not installed flask-WTF (missing csrf decorator)
|
except (ImportError, AttributeError): # Catch also error for not installed flask-WTF (missing csrf decorator)
|
||||||
kobo_available = False
|
kobo_available = False
|
||||||
|
@ -56,6 +62,7 @@ def main():
|
||||||
app.register_blueprint(tasks)
|
app.register_blueprint(tasks)
|
||||||
app.register_blueprint(web)
|
app.register_blueprint(web)
|
||||||
app.register_blueprint(opds)
|
app.register_blueprint(opds)
|
||||||
|
limiter.limit("3/minute",key_func=request_username)(opds)
|
||||||
app.register_blueprint(jinjia)
|
app.register_blueprint(jinjia)
|
||||||
app.register_blueprint(about)
|
app.register_blueprint(about)
|
||||||
app.register_blueprint(shelf)
|
app.register_blueprint(shelf)
|
||||||
|
@ -67,6 +74,7 @@ def main():
|
||||||
if kobo_available:
|
if kobo_available:
|
||||||
app.register_blueprint(kobo)
|
app.register_blueprint(kobo)
|
||||||
app.register_blueprint(kobo_auth)
|
app.register_blueprint(kobo_auth)
|
||||||
|
limiter.limit("3/minute", key_func=get_remote_address)(kobo)
|
||||||
if oauth_available:
|
if oauth_available:
|
||||||
app.register_blueprint(oauth)
|
app.register_blueprint(oauth)
|
||||||
success = web_server.start()
|
success = web_server.start()
|
||||||
|
|
|
@ -27,8 +27,7 @@ from .tasks.metadata_backup import TaskBackupMetadata
|
||||||
|
|
||||||
def get_scheduled_tasks(reconnect=True):
|
def get_scheduled_tasks(reconnect=True):
|
||||||
tasks = list()
|
tasks = list()
|
||||||
# config.schedule_reconnect or
|
# Reconnect Calibre database (metadata.db) based on config.schedule_reconnect
|
||||||
# Reconnect Calibre database (metadata.db)
|
|
||||||
if reconnect:
|
if reconnect:
|
||||||
tasks.append([lambda: TaskReconnectDatabase(), 'reconnect', False])
|
tasks.append([lambda: TaskReconnectDatabase(), 'reconnect', False])
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import errno
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import subprocess # nosec
|
import subprocess # nosec
|
||||||
from .services.background_scheduler import BackgroundScheduler
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from gevent.pywsgi import WSGIServer
|
from gevent.pywsgi import WSGIServer
|
||||||
|
@ -270,6 +269,7 @@ class WebServer(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def shutdown_scheduler():
|
def shutdown_scheduler():
|
||||||
|
from .services.background_scheduler import BackgroundScheduler
|
||||||
scheduler = BackgroundScheduler()
|
scheduler = BackgroundScheduler()
|
||||||
if scheduler:
|
if scheduler:
|
||||||
scheduler.scheduler.shutdown()
|
scheduler.scheduler.shutdown()
|
||||||
|
|
|
@ -44,15 +44,15 @@ def init_app(app, config):
|
||||||
app.config['LDAP_SCHEMA'] = 'ldap'
|
app.config['LDAP_SCHEMA'] = 'ldap'
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
||||||
if config.config_ldap_serv_password is None:
|
if config.config_ldap_serv_password_e is None:
|
||||||
config.config_ldap_serv_password = ''
|
config.config_ldap_serv_password_e = ''
|
||||||
app.config['LDAP_PASSWORD'] = base64.b64decode(config.config_ldap_serv_password)
|
app.config['LDAP_PASSWORD'] = config.config_ldap_serv_password_e
|
||||||
else:
|
else:
|
||||||
app.config['LDAP_PASSWORD'] = base64.b64decode("")
|
app.config['LDAP_PASSWORD'] = ""
|
||||||
app.config['LDAP_USERNAME'] = config.config_ldap_serv_username
|
app.config['LDAP_USERNAME'] = config.config_ldap_serv_username
|
||||||
else:
|
else:
|
||||||
app.config['LDAP_USERNAME'] = ""
|
app.config['LDAP_USERNAME'] = ""
|
||||||
app.config['LDAP_PASSWORD'] = base64.b64decode("")
|
app.config['LDAP_PASSWORD'] = ""
|
||||||
if bool(config.config_ldap_cert_path):
|
if bool(config.config_ldap_cert_path):
|
||||||
app.config['LDAP_CUSTOM_OPTIONS'].update({
|
app.config['LDAP_CUSTOM_OPTIONS'].update({
|
||||||
pyLDAP.OPT_X_TLS_REQUIRE_CERT: pyLDAP.OPT_X_TLS_DEMAND,
|
pyLDAP.OPT_X_TLS_REQUIRE_CERT: pyLDAP.OPT_X_TLS_DEMAND,
|
||||||
|
|
|
@ -434,3 +434,7 @@ div.log {
|
||||||
#detailcover:-moz-full-screen { cursor:zoom-out; border: 0; }
|
#detailcover:-moz-full-screen { cursor:zoom-out; border: 0; }
|
||||||
#detailcover:-ms-fullscreen { cursor:zoom-out; border: 0; }
|
#detailcover:-ms-fullscreen { cursor:zoom-out; border: 0; }
|
||||||
#detailcover:fullscreen { cursor:zoom-out; border: 0; }
|
#detailcover:fullscreen { cursor:zoom-out; border: 0; }
|
||||||
|
|
||||||
|
.error-list {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
1
cps/static/js/libs/pwstrength/i18next.min.js
vendored
Normal file
1
cps/static/js/libs/pwstrength/i18next.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cps/static/js/libs/pwstrength/i18nextHttpBackend.min.js
vendored
Normal file
1
cps/static/js/libs/pwstrength/i18nextHttpBackend.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
17
cps/static/js/libs/pwstrength/locales/ar.json
Normal file
17
cps/static/js/libs/pwstrength/locales/ar.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "كلمة المرور قصيرة جداً",
|
||||||
|
"wordMaxLength": "كلمة المرور طويلة جدا",
|
||||||
|
"wordInvalidChar": "تحتوي كلمة المرور على رموز غير صالحة",
|
||||||
|
"wordNotEmail": "لا تستخدم بريدك الإلكتروني ككلمة مرور",
|
||||||
|
"wordSimilarToUsername": "لا يمكن ان تحتوي كلمة المرور على إسم المستخدم",
|
||||||
|
"wordTwoCharacterClasses": "إستخدم فئات أحرف مختلفة",
|
||||||
|
"wordRepetitions": "تكرارات كثيرة",
|
||||||
|
"wordSequences": "تحتوي كلمة المرور على أنماط متتابعة",
|
||||||
|
"errorList": "الأخطاء:",
|
||||||
|
"veryWeak": "ضعيفة جداً",
|
||||||
|
"weak": "ضعيفة",
|
||||||
|
"normal": "عادية",
|
||||||
|
"medium": "متوسطة",
|
||||||
|
"strong": "قوية",
|
||||||
|
"veryStrong": "قوية جداً"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/cs.json
Normal file
17
cps/static/js/libs/pwstrength/locales/cs.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Vaše heslo je příliš krátké",
|
||||||
|
"wordMaxLength": "Vaše heslo je příliš dlouhé",
|
||||||
|
"wordInvalidChar": "Vaše heslo obsahuje neplatný znak",
|
||||||
|
"wordNotEmail": "Nepoužívejte Váš email jako Vaše heslo",
|
||||||
|
"wordSimilarToUsername": "Vaše heslo nesmí obsahovat přihlašovací jméno",
|
||||||
|
"wordTwoCharacterClasses": "Použijte různé druhy znaků",
|
||||||
|
"wordRepetitions": "Příliš mnoho opakování",
|
||||||
|
"wordSequences": "Vaše heslo obsahuje postupnost",
|
||||||
|
"errorList": "Chyby:",
|
||||||
|
"veryWeak": "Velmi slabé",
|
||||||
|
"weak": "Slabé",
|
||||||
|
"normal": "Normální",
|
||||||
|
"medium": "Středně silné",
|
||||||
|
"strong": "Silné",
|
||||||
|
"veryStrong": "Velmi silné"
|
||||||
|
}
|
21
cps/static/js/libs/pwstrength/locales/de.json
Normal file
21
cps/static/js/libs/pwstrength/locales/de.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Das Passwort ist zu kurz",
|
||||||
|
"wordMaxLength": "Das Passwort ist zu lang",
|
||||||
|
"wordInvalidChar": "Das Passwort enthält ein ungültiges Zeichen",
|
||||||
|
"wordNotEmail": "Das Passwort darf die E-Mail Adresse nicht enthalten",
|
||||||
|
"wordSimilarToUsername": "Das Passwort darf den Benutzernamen nicht enthalten",
|
||||||
|
"wordTwoCharacterClasses": "Bitte Buchstaben und Ziffern verwenden",
|
||||||
|
"wordRepetitions": "Zu viele Wiederholungen",
|
||||||
|
"wordSequences": "Das Passwort enthält Buchstabensequenzen",
|
||||||
|
"wordLowercase": "Bitte mindestens einen Kleinbuchstaben verwenden",
|
||||||
|
"wordUppercase": "Bitte mindestens einen Großbuchstaben verwenden",
|
||||||
|
"wordOneNumber": "Bitte mindestens eine Ziffern verwenden",
|
||||||
|
"wordOneSpecialChar": "Bitte mindestens ein Sonderzeichen verwenden",
|
||||||
|
"errorList": "Fehler:",
|
||||||
|
"veryWeak": "Sehr schwach",
|
||||||
|
"weak": "Schwach",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Mittel",
|
||||||
|
"strong": "Stark",
|
||||||
|
"veryStrong": "Sehr stark"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/el.json
Normal file
17
cps/static/js/libs/pwstrength/locales/el.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Ο κωδικός πρόσβασης δεν έχει τον ελάχιστο αριθμό χαρακτήρων",
|
||||||
|
"wordMaxLength": "Ο κωδικός πρόσβασής σας είναι πολύ μεγάλος",
|
||||||
|
"wordInvalidChar": "Ο κωδικός πρόσβασής σας περιέχει έναν μη έγκυρο χαρακτήρα",
|
||||||
|
"wordNotEmail": "Μη χρησιμοποιείτε το email ως κωδικό",
|
||||||
|
"wordSimilarToUsername": "Ο κωδικός πρόσβασης δεν πρέπει να περιέχει το username",
|
||||||
|
"wordTwoCharacterClasses": "Χρησιμοποιήστε διαφορετικές κλάσεις χαρακτήρων",
|
||||||
|
"wordRepetitions": "Πολλές επαναλήψεις",
|
||||||
|
"wordSequences": "Ο κωδικός πρόσβασης περιέχει επαναλήψεις",
|
||||||
|
"errorList": "Σφάλματα:",
|
||||||
|
"veryWeak": "Πολύ Αδύνατος",
|
||||||
|
"weak": "Αδύνατος",
|
||||||
|
"normal": "Κανονικός",
|
||||||
|
"medium": "Μέτριος",
|
||||||
|
"strong": "Δυνατός",
|
||||||
|
"veryStrong": "Πολύ Δυνατός"
|
||||||
|
}
|
21
cps/static/js/libs/pwstrength/locales/en.json
Normal file
21
cps/static/js/libs/pwstrength/locales/en.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Your password is too short",
|
||||||
|
"wordMaxLength": "Your password is too long",
|
||||||
|
"wordInvalidChar": "Your password contains an invalid character",
|
||||||
|
"wordNotEmail": "Do not use your email as your password",
|
||||||
|
"wordSimilarToUsername": "Your password cannot contain your username",
|
||||||
|
"wordTwoCharacterClasses": "Use different character classes",
|
||||||
|
"wordRepetitions": "Too many repetitions",
|
||||||
|
"wordSequences": "Your password contains sequences",
|
||||||
|
"wordLowercase": "Use at least one lowercase character",
|
||||||
|
"wordUppercase": "Use at least one uppercase character",
|
||||||
|
"wordOneNumber": "Use at least one number",
|
||||||
|
"wordOneSpecialChar": "Use at least one special character",
|
||||||
|
"errorList": "Errors:",
|
||||||
|
"veryWeak": "Very Weak",
|
||||||
|
"weak": "Weak",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Medium",
|
||||||
|
"strong": "Strong",
|
||||||
|
"veryStrong": "Very Strong"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/eo.json
Normal file
17
cps/static/js/libs/pwstrength/locales/eo.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Via pasvorto estas tro mallonga",
|
||||||
|
"wordMaxLength": "Via pasvorto estas tro longa",
|
||||||
|
"wordInvalidChar": "Via pasvorto enhavas nevalidan karaktero",
|
||||||
|
"wordNotEmail": "Ne uzu vian retpoŝtadreson kiel la pasvorton",
|
||||||
|
"wordSimilarToUsername": "Via pasvorto enhavas vian uzanto-nomon",
|
||||||
|
"wordTwoCharacterClasses": "Uzu signojn de diversaj tipoj (ekz., literoj kaj ciferoj)",
|
||||||
|
"wordRepetitions": "Tro multaj ripetiĝantaj signoj",
|
||||||
|
"wordSequences": "Via pasvorto enhavas simplan sinsekvon de signoj",
|
||||||
|
"errorList": "Eraroj:",
|
||||||
|
"veryWeak": "Trosimpla",
|
||||||
|
"weak": "Malforta",
|
||||||
|
"normal": "Mezforta",
|
||||||
|
"medium": "Akceptebla",
|
||||||
|
"strong": "Forta",
|
||||||
|
"veryStrong": "Elstare Forta"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/es.json
Normal file
17
cps/static/js/libs/pwstrength/locales/es.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Tu contraseña es demasiado corta",
|
||||||
|
"wordMaxLength": "Tu contraseña es muy larga",
|
||||||
|
"wordInvalidChar": "Tu contraseña contiene un carácter no válido",
|
||||||
|
"wordNotEmail": "No uses tu email como tu contraseña",
|
||||||
|
"wordSimilarToUsername": "Tu contraseña no puede contener tu nombre de usuario",
|
||||||
|
"wordTwoCharacterClasses": "Mezcla diferentes clases de caracteres",
|
||||||
|
"wordRepetitions": "Demasiadas repeticiones",
|
||||||
|
"wordSequences": "Tu contraseña contiene secuencias",
|
||||||
|
"errorList": "Errores:",
|
||||||
|
"veryWeak": "Muy Débil",
|
||||||
|
"weak": "Débil",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Media",
|
||||||
|
"strong": "Fuerte",
|
||||||
|
"veryStrong": "Muy Fuerte"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/fr.json
Normal file
17
cps/static/js/libs/pwstrength/locales/fr.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Votre mot de passe est trop court",
|
||||||
|
"wordMaxLength": "Votre mot de passe est trop long",
|
||||||
|
"wordInvalidChar": "Votre mot de passe contient un caractère invalide",
|
||||||
|
"wordNotEmail": "Ne pas utiliser votre adresse e-mail comme mot de passe",
|
||||||
|
"wordSimilarToUsername": "Votre mot de passe ne peut pas contenir votre nom d'utilisateur",
|
||||||
|
"wordTwoCharacterClasses": "Utilisez différents type de caractères",
|
||||||
|
"wordRepetitions": "Trop de répétitions",
|
||||||
|
"wordSequences": "Votre mot de passe contient des séquences",
|
||||||
|
"errorList": "Erreurs:",
|
||||||
|
"veryWeak": "Très Faible",
|
||||||
|
"weak": "Faible",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Moyen",
|
||||||
|
"strong": "Fort",
|
||||||
|
"veryStrong": "Très Fort"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/it.json
Normal file
17
cps/static/js/libs/pwstrength/locales/it.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "La tua password è troppo corta",
|
||||||
|
"wordMaxLength": "La tua password è troppo lunga",
|
||||||
|
"wordInvalidChar": "La tua password contiene un carattere non valido",
|
||||||
|
"wordNotEmail": "Non usare la tua e-mail come password",
|
||||||
|
"wordSimilarToUsername": "La tua password non può contenere il tuo nome",
|
||||||
|
"wordTwoCharacterClasses": "Usa classi di caratteri diversi",
|
||||||
|
"wordRepetitions": "Troppe ripetizioni",
|
||||||
|
"wordSequences": "La tua password contiene sequenze",
|
||||||
|
"errorList": "Errori:",
|
||||||
|
"veryWeak": "Molto debole",
|
||||||
|
"weak": "Debole",
|
||||||
|
"normal": "Normale",
|
||||||
|
"medium": "Media",
|
||||||
|
"strong": "Forte",
|
||||||
|
"veryStrong": "Molto forte"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/no.json
Normal file
17
cps/static/js/libs/pwstrength/locales/no.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Ditt passord er for kort",
|
||||||
|
"wordMaxLength": "Ditt passord er for langt",
|
||||||
|
"wordInvalidChar": "Ditt passord inneholder et ugyldig tegn",
|
||||||
|
"wordNotEmail": "Ikke bruk din epost som ditt passord",
|
||||||
|
"wordSimilarToUsername": "Ditt passord er for likt ditt brukernavn",
|
||||||
|
"wordTwoCharacterClasses": "Bruk en kombinasjon av bokstaver, tall og andre tegn",
|
||||||
|
"wordRepetitions": "For mange repitisjoner",
|
||||||
|
"wordSequences": "Ditt passord inneholder repeterende tegn",
|
||||||
|
"errorList": "Feil:",
|
||||||
|
"veryWeak": "Veldig Svakt",
|
||||||
|
"weak": "Svakt",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Medium",
|
||||||
|
"strong": "Sterkt",
|
||||||
|
"veryStrong": "Veldig Sterkt"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/pl.json
Normal file
17
cps/static/js/libs/pwstrength/locales/pl.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Hasło jest zbyt krótkie",
|
||||||
|
"wordMaxLength": "Hasło jest za długie",
|
||||||
|
"wordInvalidChar": "Hasło zawiera nieprawidłowy znak",
|
||||||
|
"wordNotEmail": "Hasło nie może być Twoim emailem",
|
||||||
|
"wordSimilarToUsername": "Hasło nie może zawierać nazwy użytkownika",
|
||||||
|
"wordTwoCharacterClasses": "Użyj innych klas znaków",
|
||||||
|
"wordRepetitions": "Zbyt wiele powtórzeń",
|
||||||
|
"wordSequences": "Hasło zawiera sekwencje",
|
||||||
|
"errorList": "Błędy:",
|
||||||
|
"veryWeak": "Bardzo słabe",
|
||||||
|
"weak": "Słabe",
|
||||||
|
"normal": "Normalne",
|
||||||
|
"medium": "Średnie",
|
||||||
|
"strong": "Silne",
|
||||||
|
"veryStrong": "Bardzo silne"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/pt.json
Normal file
17
cps/static/js/libs/pwstrength/locales/pt.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Sua senha é muito curta",
|
||||||
|
"wordMaxLength": "Sua senha é muito longa",
|
||||||
|
"wordInvalidChar": "Sua senha contém um caractere inválido",
|
||||||
|
"wordNotEmail": "Não use seu e-mail como senha",
|
||||||
|
"wordSimilarToUsername": "Sua senha não pode conter o seu nome de usuário",
|
||||||
|
"wordTwoCharacterClasses": "Use diferentes classes de caracteres",
|
||||||
|
"wordRepetitions": "Muitas repetições",
|
||||||
|
"wordSequences": "Sua senha contém sequências",
|
||||||
|
"errorList": "Erros:",
|
||||||
|
"veryWeak": "Muito Fraca",
|
||||||
|
"weak": "Fraca",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Média",
|
||||||
|
"strong": "Forte",
|
||||||
|
"veryStrong": "Muito Forte"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/ru.json
Normal file
17
cps/static/js/libs/pwstrength/locales/ru.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Слишком короткий пароль",
|
||||||
|
"wordMaxLength": "Ваш пароль слишком длинный",
|
||||||
|
"wordInvalidChar": "Ваш пароль содержит недопустимый символ",
|
||||||
|
"wordNotEmail": "Не используйте e-mail в качестве пароля",
|
||||||
|
"wordSimilarToUsername": "Пароль не должен содержать логин",
|
||||||
|
"wordTwoCharacterClasses": "Используйте разные классы символов",
|
||||||
|
"wordRepetitions": "Слишком много повторений",
|
||||||
|
"wordSequences": "Пароль содержит последовательности",
|
||||||
|
"errorList": "Ошибки:",
|
||||||
|
"veryWeak": "Очень слабый",
|
||||||
|
"weak": "Слабый",
|
||||||
|
"normal": "Нормальный",
|
||||||
|
"medium": "Средний",
|
||||||
|
"strong": "Сильный",
|
||||||
|
"veryStrong": "Очень сильный"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/sk.json
Normal file
17
cps/static/js/libs/pwstrength/locales/sk.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Vaše heslo je príliž krátke",
|
||||||
|
"wordMaxLength": "Vaše heslo je príliš dlhé",
|
||||||
|
"wordInvalidChar": "Vaše heslo obsahuje neplatný znak",
|
||||||
|
"wordNotEmail": "Nepoužívajte Váš email ako Vaše heslo",
|
||||||
|
"wordSimilarToUsername": "Vaše heslo nesmie obsahovať prihlasovacie meno",
|
||||||
|
"wordTwoCharacterClasses": "Použite rôzne druhy znakov",
|
||||||
|
"wordRepetitions": "Príliš veľa opakovaní",
|
||||||
|
"wordSequences": "Vaše heslo obsahuje postupnosť",
|
||||||
|
"errorList": "Chyby:",
|
||||||
|
"veryWeak": "Veľmi slabé",
|
||||||
|
"weak": "Slabé",
|
||||||
|
"normal": "Normálne",
|
||||||
|
"medium": "Stredne silné",
|
||||||
|
"strong": "Silné",
|
||||||
|
"veryStrong": "Veľmi silné"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/th.json
Normal file
17
cps/static/js/libs/pwstrength/locales/th.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "รหัสผ่านของคุณสั้นเกินไป",
|
||||||
|
"wordMaxLength": "รหัสผ่านของคุณยาวเกินไป",
|
||||||
|
"wordInvalidChar": "รหัสผ่านของคุณมีอักษรที่ไม่ถูกต้อง",
|
||||||
|
"wordNotEmail": "คุณไม่สามารถใช้รหัสผ่านเหมือนกับอีเมล์ของคุณได้",
|
||||||
|
"wordSimilarToUsername": "รหัสผ่านไม่ควรประกอบด้วยคำที่เป็น username",
|
||||||
|
"wordTwoCharacterClasses": "ลองเป็นกลุ่มคำใหม่",
|
||||||
|
"wordRepetitions": "มีอักษรซ้ำเยอะเกินไป",
|
||||||
|
"wordSequences": "รหัสผ่านของคุณเดาง่ายเกินไป",
|
||||||
|
"errorList": "Errors:",
|
||||||
|
"veryWeak": "เดาง่ายมาก",
|
||||||
|
"weak": "เดาง่าย",
|
||||||
|
"normal": "พอใช้",
|
||||||
|
"medium": "กำลังดี",
|
||||||
|
"strong": "ค่อนข้างดี",
|
||||||
|
"veryStrong": "ดีมาก"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/tr.json
Normal file
17
cps/static/js/libs/pwstrength/locales/tr.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "Girdiğiniz şifre çok Kısa",
|
||||||
|
"wordMaxLength": "Parolanız çok uzun",
|
||||||
|
"wordInvalidChar": "Şifreniz geçersiz bir karakter içeriyor",
|
||||||
|
"wordNotEmail": "E-mail adresinizi şifreniz içerisinde kullanmayınız",
|
||||||
|
"wordSimilarToUsername": "Kullanıcı Adınızı şifreniz içerisinde kullanmayınız",
|
||||||
|
"wordTwoCharacterClasses": "Başka karakter sınıfı kullanınız",
|
||||||
|
"wordRepetitions": "Çok fazla tekrar var",
|
||||||
|
"wordSequences": "Şifreniz Dizi içermektedir",
|
||||||
|
"errorList": "Hatalar:",
|
||||||
|
"veryWeak": "Çok Zayıf",
|
||||||
|
"weak": "Zayıf",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Orta",
|
||||||
|
"strong": "Güçlü",
|
||||||
|
"veryStrong": "Çok Güçlü"
|
||||||
|
}
|
17
cps/static/js/libs/pwstrength/locales/zh-TW.json
Normal file
17
cps/static/js/libs/pwstrength/locales/zh-TW.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"wordMinLength": "您的密碼太短",
|
||||||
|
"wordMaxLength": "您的密碼太長",
|
||||||
|
"wordInvalidChar": "您的密碼包含無效字符",
|
||||||
|
"wordNotEmail": "不要使用電子郵件作為密碼",
|
||||||
|
"wordSimilarToUsername": "您的密碼不能包含您的用戶名",
|
||||||
|
"wordTwoCharacterClasses": "使用不同的字元類型 例如: 大小寫混合",
|
||||||
|
"wordRepetitions": "太多的重複。例如:1111",
|
||||||
|
"wordSequences": "你的密碼包含連續英/數字 例如:123 or abc",
|
||||||
|
"errorList": "錯誤:",
|
||||||
|
"veryWeak": "非常弱",
|
||||||
|
"weak": "弱",
|
||||||
|
"normal": "普通",
|
||||||
|
"medium": "中等",
|
||||||
|
"strong": "強",
|
||||||
|
"veryStrong": "非常強"
|
||||||
|
}
|
1223
cps/static/js/libs/pwstrength/pwstrength-bootstrap.js
Normal file
1223
cps/static/js/libs/pwstrength/pwstrength-bootstrap.js
Normal file
File diff suppressed because it is too large
Load Diff
4
cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js
vendored
Normal file
4
cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
65
cps/static/js/password.js
Normal file
65
cps/static/js/password.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
* Copyright (C) 2022 OzzieIsaacs
|
||||||
|
*
|
||||||
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
$(document).ready(function() {
|
||||||
|
i18next.use(i18nextHttpBackend).init({
|
||||||
|
lng: $('#password').data("lang"),
|
||||||
|
debug: false,
|
||||||
|
fallbackLng: 'en',
|
||||||
|
backend: {
|
||||||
|
loadPath: getPath() + "/static/js/libs/pwstrength/locales/{{lng}}.json",
|
||||||
|
},
|
||||||
|
|
||||||
|
}, function () {
|
||||||
|
if ($('#password').data("verify")) {
|
||||||
|
// Initialized and ready to go
|
||||||
|
var options = {};
|
||||||
|
options.common = {
|
||||||
|
minChar: $('#password').data("min"),
|
||||||
|
maxChar: -1
|
||||||
|
}
|
||||||
|
options.ui = {
|
||||||
|
bootstrap3: true,
|
||||||
|
showProgressBar: false,
|
||||||
|
showErrors: true,
|
||||||
|
showVerdicts: false,
|
||||||
|
}
|
||||||
|
options.rules= {
|
||||||
|
specialCharClass: "(?=.*?[^A-Za-z\\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,
|
||||||
|
wordOneNumber: $('#password').data("number") === "True" ? true : false,
|
||||||
|
wordThreeNumbers: false,
|
||||||
|
wordOneSpecialChar: $('#password').data("special") === "True" ? true : false,
|
||||||
|
// wordTwoSpecialChar: true,
|
||||||
|
wordUpperLowerCombo: false,
|
||||||
|
wordLetterNumberCombo: false,
|
||||||
|
wordLetterNumberCharCombo: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('#password').pwstrength(options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -634,7 +634,7 @@ function UserActions (value, row) {
|
||||||
|
|
||||||
/* Function for cancelling tasks */
|
/* Function for cancelling tasks */
|
||||||
function TaskActions (value, row) {
|
function TaskActions (value, row) {
|
||||||
var cancellableStats = [0, 1, 2];
|
var cancellableStats = [0, 2];
|
||||||
if (row.task_id && row.is_cancellable && cancellableStats.includes(row.stat)) {
|
if (row.task_id && row.is_cancellable && cancellableStats.includes(row.stat)) {
|
||||||
return [
|
return [
|
||||||
"<div class=\"danger task-cancel\" data-toggle=\"modal\" data-target=\"#cancelTaskModal\" data-task-id=\"" + row.task_id + "\" title=\"Cancel\">",
|
"<div class=\"danger task-cancel\" data-toggle=\"modal\" data-target=\"#cancelTaskModal\" data-task-id=\"" + row.task_id + "\" title=\"Cancel\">",
|
||||||
|
|
|
@ -202,8 +202,8 @@ class TaskEmail(CalibreTask):
|
||||||
self.asyncSMTP.set_debuglevel(1)
|
self.asyncSMTP.set_debuglevel(1)
|
||||||
if use_ssl == 1:
|
if use_ssl == 1:
|
||||||
self.asyncSMTP.starttls()
|
self.asyncSMTP.starttls()
|
||||||
if self.settings["mail_password"]:
|
if self.settings["mail_password_e"]:
|
||||||
self.asyncSMTP.login(str(self.settings["mail_login"]), str(self.settings["mail_password"]))
|
self.asyncSMTP.login(str(self.settings["mail_login"]), str(self.settings["mail_password_e"]))
|
||||||
|
|
||||||
# Convert message to something to send
|
# Convert message to something to send
|
||||||
fp = StringIO()
|
fp = StringIO()
|
||||||
|
|
|
@ -22,8 +22,8 @@ from urllib.request import urlopen
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from html import escape
|
from html import escape
|
||||||
|
|
||||||
from cps import config, db, fs, gdriveutils, logger, ub
|
from cps import config, db, gdriveutils, logger
|
||||||
from cps.services.worker import CalibreTask, STAT_CANCELLED, STAT_ENDED
|
from cps.services.worker import CalibreTask
|
||||||
from flask_babel import lazy_gettext as N_
|
from flask_babel import lazy_gettext as N_
|
||||||
|
|
||||||
OPF_NAMESPACE = "http://www.idpf.org/2007/opf"
|
OPF_NAMESPACE = "http://www.idpf.org/2007/opf"
|
||||||
|
@ -74,7 +74,10 @@ class TaskBackupMetadata(CalibreTask):
|
||||||
def backup_metadata(self):
|
def backup_metadata(self):
|
||||||
try:
|
try:
|
||||||
metadata_backup = self.calibre_db.session.query(db.Metadata_Dirtied).all()
|
metadata_backup = self.calibre_db.session.query(db.Metadata_Dirtied).all()
|
||||||
custom_columns = self.calibre_db.session.query(db.CustomColumns).order_by(db.CustomColumns.label).all()
|
custom_columns = (self.calibre_db.session.query(db.CustomColumns)
|
||||||
|
.filter(db.CustomColumns.mark_for_delete == 0)
|
||||||
|
.filter(db.CustomColumns.datatype.notin_(db.cc_exceptions))
|
||||||
|
.order_by(db.CustomColumns.label).all())
|
||||||
count = len(metadata_backup)
|
count = len(metadata_backup)
|
||||||
i = 0
|
i = 0
|
||||||
for backup in metadata_backup:
|
for backup in metadata_backup:
|
||||||
|
@ -226,11 +229,11 @@ class TaskBackupMetadata(CalibreTask):
|
||||||
# doc = etree.tostring(package, xml_declaration=True, encoding='utf-8', pretty_print=True) # .replace(b"&quot;", b""")
|
# doc = etree.tostring(package, xml_declaration=True, encoding='utf-8', pretty_print=True) # .replace(b"&quot;", b""")
|
||||||
try:
|
try:
|
||||||
with open(book_metadata_filepath, 'wb') as f:
|
with open(book_metadata_filepath, 'wb') as f:
|
||||||
# f.write(doc)
|
|
||||||
doc.write(f, xml_declaration=True, encoding='utf-8', pretty_print=True)
|
doc.write(f, xml_declaration=True, encoding='utf-8', pretty_print=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
# ToDo: Folder not writeable error
|
# ToDo: Folder not writeable error
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return "Metadata backup"
|
return "Metadata backup"
|
||||||
|
|
|
@ -61,27 +61,27 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2>{{_('Email Server Settings')}}</h2>
|
<h2>{{_('Email Server Settings')}}</h2>
|
||||||
{% if config.get_mail_server_configured() %}
|
{% if config.get_mail_server_configured() %}
|
||||||
{% if email.mail_server_type == 0 %}
|
{% if config.mail_server_type == 0 %}
|
||||||
<div class="col-xs-12 col-sm-12">
|
<div class="col-xs-12 col-sm-12">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-3">{{_('SMTP Hostname')}}</div>
|
<div class="col-xs-6 col-sm-3">{{_('SMTP Hostname')}}</div>
|
||||||
<div class="col-xs-6 col-sm-3">{{email.mail_server}}</div>
|
<div class="col-xs-6 col-sm-3">{{config.mail_server}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-3">{{_('SMTP Port')}}</div>
|
<div class="col-xs-6 col-sm-3">{{_('SMTP Port')}}</div>
|
||||||
<div class="col-xs-6 col-sm-3">{{email.mail_port}}</div>
|
<div class="col-xs-6 col-sm-3">{{config.mail_port}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-3">{{_('Encryption')}}</div>
|
<div class="col-xs-6 col-sm-3">{{_('Encryption')}}</div>
|
||||||
<div class="col-xs-6 col-sm-3">{{ display_bool_setting(email.mail_use_ssl) }}</div>
|
<div class="col-xs-6 col-sm-3">{{ display_bool_setting(config.mail_use_ssl) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-3">{{_('SMTP Login')}}</div>
|
<div class="col-xs-6 col-sm-3">{{_('SMTP Login')}}</div>
|
||||||
<div class="col-xs-6 col-sm-3">{{email.mail_login}}</div>
|
<div class="col-xs-6 col-sm-3">{{config.mail_login}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-3">{{_('From Email')}}</div>
|
<div class="col-xs-6 col-sm-3">{{_('From Email')}}</div>
|
||||||
<div class="col-xs-6 col-sm-3">{{email.mail_from}}</div>
|
<div class="col-xs-6 col-sm-3">{{config.mail_from}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-sm-3">{{_('From Email')}}</div>
|
<div class="col-xs-6 col-sm-3">{{_('From Email')}}</div>
|
||||||
<div class="col-xs-6 col-sm-3">{{email.mail_gmail_token['email']}}</div>
|
<div class="col-xs-6 col-sm-3">{{config.mail_gmail_token['email']}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -159,8 +159,8 @@
|
||||||
<input type="text" class="form-control" id="config_goodreads_api_key" name="config_goodreads_api_key" value="{% if config.config_goodreads_api_key != None %}{{ config.config_goodreads_api_key }}{% endif %}" autocomplete="off">
|
<input type="text" class="form-control" id="config_goodreads_api_key" name="config_goodreads_api_key" value="{% if config.config_goodreads_api_key != None %}{{ config.config_goodreads_api_key }}{% endif %}" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="config_goodreads_api_secret">{{_('Goodreads API Secret')}}</label>
|
<label for="config_goodreads_api_secret_e">{{_('Goodreads API Secret')}}</label>
|
||||||
<input type="text" class="form-control" id="config_goodreads_api_secret" name="config_goodreads_api_secret" value="{% if config.config_goodreads_api_secret != None %}{{ config.config_goodreads_api_secret }}{% endif %}" autocomplete="off">
|
<input type="password" class="form-control" id="config_goodreads_api_secret_e" name="config_goodreads_api_secret_e" value="{% if config.config_goodreads_api_secret_e != None %}{{ config.config_goodreads_api_secret_e }}{% endif %}" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -245,8 +245,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div data-related="ldap-auth-password-2">
|
<div data-related="ldap-auth-password-2">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="config_ldap_serv_password">{{_('LDAP Administrator Password')}}</label>
|
<label for="config_ldap_serv_password_e">{{_('LDAP Administrator Password')}}</label>
|
||||||
<input type="password" class="form-control" id="config_ldap_serv_password" name="config_ldap_serv_password" value="" autocomplete="off">
|
<input type="password" class="form-control" id="config_ldap_serv_password_e" name="config_ldap_serv_password_e" value="" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -353,6 +353,57 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<a class="accordion-toggle" data-toggle="collapse" href="#collapsesix">
|
||||||
|
<span class="glyphicon glyphicon-plus"></span>
|
||||||
|
{{_('Securitiy Settings')}}
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="collapsesix" class="panel-collapse collapse">
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="config_ratelimiter" name="config_ratelimiter" {% if config.config_ratelimiter %}checked{% endif %}>
|
||||||
|
<label for="config_ratelimiter">{{_('Limit failed login attempts')}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="config_session">{{_('Session protection')}}</label>
|
||||||
|
<select name="config_session" id="config_session" class="form-control">
|
||||||
|
<option value="0" {% if config.config_session == 0 %}selected{% endif %}>{{_('Basic')}}</option>
|
||||||
|
<option value="1" {% if config.config_session == 1 %}selected{% endif %}>{{_('Strong')}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="config_password_policy" data-control="password_settings" name="config_password_policy" {% if config.config_password_policy %}checked{% endif %}>
|
||||||
|
<label for="config_password_policy">{{_('User Password policy')}}</label>
|
||||||
|
</div>
|
||||||
|
<div data-related="password_settings">
|
||||||
|
<div class="form-group" style="margin-left:10px;">
|
||||||
|
<label for="config_password_min_length">{{_('Minimum password length')}}</label>
|
||||||
|
<input type="number" min="1" max="40" class="form-control" name="config_password_min_length" id="config_password_min_length" value="{% if config.config_password_min_length != None %}{{ config.config_password_min_length }}{% endif %}" autocomplete="off" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-left:10px;">
|
||||||
|
<input type="checkbox" id="config_password_number" name="config_password_number" {% if config.config_password_number %}checked{% endif %}>
|
||||||
|
<label for="config_password_number">{{_('Enforce number')}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-left:10px;">
|
||||||
|
<input type="checkbox" id="config_password_lower" name="config_password_lower" {% if config.config_password_lower %}checked{% endif %}>
|
||||||
|
<label for="config_password_lower">{{_('Enforce lowercase characters')}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-left:10px;">
|
||||||
|
<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_special" name="config_password_special" {% if config.config_password_special %}checked{% endif %}>
|
||||||
|
<label for="config_password_special">{{_('Enforce special characters')}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="button" name="submit" id="config_submit" class="btn btn-default">{{_('Save')}}</button>
|
<button type="button" name="submit" id="config_submit" class="btn btn-default">{{_('Save')}}</button>
|
||||||
|
|
|
@ -48,8 +48,8 @@
|
||||||
<input type="text" class="form-control" name="mail_login" id="mail_login" value="{{content.mail_login}}">
|
<input type="text" class="form-control" name="mail_login" id="mail_login" value="{{content.mail_login}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="mail_password">{{_('SMTP Password')}}</label>
|
<label for="mail_password_e">{{_('SMTP Password')}}</label>
|
||||||
<input type="password" class="form-control" name="mail_password" id="mail_password" value="{{content.mail_password}}">
|
<input type="password" class="form-control" name="mail_password_e" id="mail_password_e" value="{{content.mail_password_e}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="mail_from">{{_('From Email')}}</label>
|
<label for="mail_from">{{_('From Email')}}</label>
|
||||||
|
|
10
cps/templates/locales/en/translation.json
Normal file
10
cps/templates/locales/en/translation.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"input": {
|
||||||
|
"placeholder": "a placeholder"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"home": "Home",
|
||||||
|
"page1": "Page One",
|
||||||
|
"page2": "Page Two"
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,11 +7,11 @@
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">{{_('Username')}}</label>
|
<label for="username">{{_('Username')}}</label>
|
||||||
<input type="text" class="form-control" id="username" name="username" autocapitalize="off" placeholder="{{_('Username')}}">
|
<input type="text" class="form-control" id="username" name="username" autocapitalize="off" placeholder="{{_('Username')}}" value="{{ username }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">{{_('Password')}}</label>
|
<label for="password">{{_('Password')}}</label>
|
||||||
<input type="password" class="form-control" id="password" name="password" placeholder="{{_('Password')}}">
|
<input type="password" class="form-control" id="password" name="password" placeholder="{{_('Password')}}" value="{{ password }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">{{_('Password')}}</label>
|
<label for="password">{{_('Password')}}</label>
|
||||||
<input type="password" class="form-control" name="password" id="password" 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-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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -175,5 +175,9 @@
|
||||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
|
<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/password.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/table.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/table.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
11
cps/ub.py
11
cps/ub.py
|
@ -55,6 +55,7 @@ from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
from . import constants, logger
|
from . import constants, logger
|
||||||
|
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
session = None
|
session = None
|
||||||
|
@ -817,7 +818,7 @@ def init_db_thread():
|
||||||
return Session()
|
return Session()
|
||||||
|
|
||||||
|
|
||||||
def init_db(app_db_path, user_credentials=None):
|
def init_db(app_db_path):
|
||||||
# Open session for database connection
|
# Open session for database connection
|
||||||
global session
|
global session
|
||||||
global app_DB_path
|
global app_DB_path
|
||||||
|
@ -838,6 +839,7 @@ def init_db(app_db_path, user_credentials=None):
|
||||||
create_admin_user(session)
|
create_admin_user(session)
|
||||||
create_anonymous_user(session)
|
create_anonymous_user(session)
|
||||||
|
|
||||||
|
def password_change(user_credentials=None):
|
||||||
if user_credentials:
|
if user_credentials:
|
||||||
username, password = user_credentials.split(':', 1)
|
username, password = user_credentials.split(':', 1)
|
||||||
user = session.query(User).filter(func.lower(User.name) == username.lower()).first()
|
user = session.query(User).filter(func.lower(User.name) == username.lower()).first()
|
||||||
|
@ -845,7 +847,12 @@ def init_db(app_db_path, user_credentials=None):
|
||||||
if not password:
|
if not password:
|
||||||
print("Empty password is not allowed")
|
print("Empty password is not allowed")
|
||||||
sys.exit(4)
|
sys.exit(4)
|
||||||
user.password = generate_password_hash(password)
|
try:
|
||||||
|
from .helper import valid_password
|
||||||
|
user.password = generate_password_hash(valid_password(password))
|
||||||
|
except Exception:
|
||||||
|
print("Password doesn't comply with password validation rules")
|
||||||
|
sys.exit(4)
|
||||||
if session_commit() == "":
|
if session_commit() == "":
|
||||||
print("Password for user '{}' changed".format(username))
|
print("Password for user '{}' changed".format(username))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
@ -401,7 +401,7 @@ class Updater(threading.Thread):
|
||||||
os.sep + 'app.db', os.sep + 'calibre-web.log1', os.sep + 'calibre-web.log2', os.sep + 'gdrive.db',
|
os.sep + 'app.db', os.sep + 'calibre-web.log1', os.sep + 'calibre-web.log2', os.sep + 'gdrive.db',
|
||||||
os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep + 'client_secrets.json',
|
os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep + 'client_secrets.json',
|
||||||
os.sep + 'gdrive_credentials', os.sep + 'settings.yaml', os.sep + 'venv', os.sep + 'virtualenv',
|
os.sep + 'gdrive_credentials', os.sep + 'settings.yaml', os.sep + 'venv', os.sep + 'virtualenv',
|
||||||
os.sep + 'access.log', os.sep + 'access.log1', os.sep + 'access.log2',
|
os.sep + 'access.log', os.sep + 'access.log1', os.sep + 'access.log2', os.sep + '.key',
|
||||||
os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so', os.sep + 'cps' + os.sep + '.HOMEDIR',
|
os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so', os.sep + 'cps' + os.sep + '.HOMEDIR',
|
||||||
os.sep + 'gmail.json', os.sep + 'exclude.txt', os.sep + 'cps' + os.sep + 'cache'
|
os.sep + 'gmail.json', os.sep + 'exclude.txt', os.sep + 'cps' + os.sep + 'cache'
|
||||||
]
|
]
|
||||||
|
|
|
@ -23,7 +23,7 @@ from werkzeug.security import check_password_hash
|
||||||
from flask_login import login_required, login_user
|
from flask_login import login_required, login_user
|
||||||
from flask import request, Response
|
from flask import request, Response
|
||||||
|
|
||||||
from . import lm, ub, config, constants, services, logger
|
from . import lm, ub, config, constants, services, logger, limiter
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ def requires_basic_auth_if_no_ano(f):
|
||||||
login_result, error = services.ldap.bind_user(auth.username, auth.password)
|
login_result, error = services.ldap.bind_user(auth.username, auth.password)
|
||||||
if login_result:
|
if login_result:
|
||||||
user = _fetch_user_by_name(auth.username)
|
user = _fetch_user_by_name(auth.username)
|
||||||
|
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
|
||||||
login_user(user)
|
login_user(user)
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
elif login_result is not None:
|
elif login_result is not None:
|
||||||
|
@ -65,8 +66,10 @@ def requires_basic_auth_if_no_ano(f):
|
||||||
|
|
||||||
|
|
||||||
def _load_user_from_auth_header(username, password):
|
def _load_user_from_auth_header(username, password):
|
||||||
|
limiter.check()
|
||||||
user = _fetch_user_by_name(username)
|
user = _fetch_user_by_name(username)
|
||||||
if bool(user and check_password_hash(str(user.password), password)):
|
if bool(user and check_password_hash(str(user.password), password)) and user.name != "Guest":
|
||||||
|
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
|
||||||
login_user(user)
|
login_user(user)
|
||||||
return user
|
return user
|
||||||
else:
|
else:
|
||||||
|
@ -101,6 +104,7 @@ def load_user_from_reverse_proxy_header(req):
|
||||||
if rp_header_username:
|
if rp_header_username:
|
||||||
user = _fetch_user_by_name(rp_header_username)
|
user = _fetch_user_by_name(rp_header_username)
|
||||||
if user:
|
if user:
|
||||||
|
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
|
||||||
login_user(user)
|
login_user(user)
|
||||||
return user
|
return user
|
||||||
return None
|
return None
|
||||||
|
|
191
cps/web.py
191
cps/web.py
|
@ -30,6 +30,8 @@ from flask import session as flask_session
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_babel import get_locale
|
from flask_babel import get_locale
|
||||||
from flask_login import login_user, logout_user, login_required, current_user
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
|
from flask_limiter import RateLimitExceeded
|
||||||
|
from flask_limiter.util import get_remote_address
|
||||||
from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError
|
from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError
|
||||||
from sqlalchemy.sql.expression import text, func, false, not_, and_, or_
|
from sqlalchemy.sql.expression import text, func, false, not_, and_, or_
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
@ -46,7 +48,7 @@ from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||||
from .helper import check_valid_domain, check_email, check_username, \
|
from .helper import check_valid_domain, check_email, check_username, \
|
||||||
get_book_cover, get_series_cover_thumbnail, get_download_link, send_mail, generate_random_password, \
|
get_book_cover, get_series_cover_thumbnail, get_download_link, send_mail, generate_random_password, \
|
||||||
send_registration_mail, check_send_to_ereader, check_read_formats, tags_filters, reset_password, valid_email, \
|
send_registration_mail, check_send_to_ereader, check_read_formats, tags_filters, reset_password, valid_email, \
|
||||||
edit_book_read_status
|
edit_book_read_status, valid_password
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
from .redirect import redirect_back
|
from .redirect import redirect_back
|
||||||
from .babel import get_available_locale
|
from .babel import get_available_locale
|
||||||
|
@ -54,9 +56,11 @@ from .usermanagement import login_required_if_no_ano
|
||||||
from .kobo_sync_status import remove_synced_book
|
from .kobo_sync_status import remove_synced_book
|
||||||
from .render_template import render_title_template
|
from .render_template import render_title_template
|
||||||
from .kobo_sync_status import change_archived_books
|
from .kobo_sync_status import change_archived_books
|
||||||
|
from . import limiter
|
||||||
from .services.worker import WorkerThread
|
from .services.worker import WorkerThread
|
||||||
from .tasks_status import render_task_status
|
from .tasks_status import render_task_status
|
||||||
|
|
||||||
|
|
||||||
feature_support = {
|
feature_support = {
|
||||||
'ldap': bool(services.ldap),
|
'ldap': bool(services.ldap),
|
||||||
'goodreads': bool(services.goodreads_support),
|
'goodreads': bool(services.goodreads_support),
|
||||||
|
@ -1230,19 +1234,23 @@ def send_to_ereader(book_id, book_format, convert):
|
||||||
|
|
||||||
# ################################### Login Logout ##################################################################
|
# ################################### Login Logout ##################################################################
|
||||||
|
|
||||||
|
@web.route('/register', methods=['POST'])
|
||||||
@web.route('/register', methods=['GET', 'POST'])
|
@limiter.limit("40/day", key_func=get_remote_address)
|
||||||
def register():
|
@limiter.limit("3/minute", key_func=get_remote_address)
|
||||||
|
def register_post():
|
||||||
if not config.config_public_reg:
|
if not config.config_public_reg:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
to_save = request.form.to_dict()
|
||||||
|
try:
|
||||||
|
limiter.check()
|
||||||
|
except RateLimitExceeded:
|
||||||
|
flash(_(u"Please wait one minute to register next user"), category="error")
|
||||||
|
return render_title_template('register.html', config=config, title=_("Register"), page="register")
|
||||||
if current_user is not None and current_user.is_authenticated:
|
if current_user is not None and current_user.is_authenticated:
|
||||||
return redirect(url_for('web.index'))
|
return redirect(url_for('web.index'))
|
||||||
if not config.get_mail_server_configured():
|
if not config.get_mail_server_configured():
|
||||||
flash(_("Oops! Email server is not configured, please contact your administrator."), category="error")
|
flash(_("Oops! Email server is not configured, please contact your administrator."), category="error")
|
||||||
return render_title_template('register.html', title=_("Register"), page="register")
|
return render_title_template('register.html', title=_("Register"), page="register")
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
to_save = request.form.to_dict()
|
|
||||||
nickname = to_save.get("email", "").strip() if config.config_register_email else to_save.get('name')
|
nickname = to_save.get("email", "").strip() if config.config_register_email else to_save.get('name')
|
||||||
if not nickname or not to_save.get("email"):
|
if not nickname or not to_save.get("email"):
|
||||||
flash(_("Oops! Please complete all fields."), category="error")
|
flash(_("Oops! Please complete all fields."), category="error")
|
||||||
|
@ -1258,7 +1266,7 @@ def register():
|
||||||
if check_valid_domain(email):
|
if check_valid_domain(email):
|
||||||
content.name = nickname
|
content.name = nickname
|
||||||
content.email = email
|
content.email = email
|
||||||
password = generate_random_password()
|
password = generate_random_password(config.config_password_min_length)
|
||||||
content.password = generate_password_hash(password)
|
content.password = generate_password_hash(password)
|
||||||
content.role = config.config_default_role
|
content.role = config.config_default_role
|
||||||
content.locale = config.config_default_locale
|
content.locale = config.config_default_locale
|
||||||
|
@ -1275,79 +1283,35 @@ def register():
|
||||||
return render_title_template('register.html', title=_("Register"), page="register")
|
return render_title_template('register.html', title=_("Register"), page="register")
|
||||||
else:
|
else:
|
||||||
flash(_("Oops! Your Email is not allowed."), category="error")
|
flash(_("Oops! Your Email is not allowed."), category="error")
|
||||||
log.warning('Registering failed for user "{}" Email: {}'.format(nickname,
|
log.warning('Registering failed for user "{}" Email: {}'.format(nickname, to_save.get("email","")))
|
||||||
to_save.get("email","")))
|
|
||||||
return render_title_template('register.html', title=_("Register"), page="register")
|
return render_title_template('register.html', title=_("Register"), page="register")
|
||||||
flash(_("Success! Confirmation Email has been sent."), category="success")
|
flash(_("Success! Confirmation Email has been sent."), category="success")
|
||||||
return redirect(url_for('web.login'))
|
return redirect(url_for('web.login'))
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/register', methods=['GET'])
|
||||||
|
def register():
|
||||||
|
if not config.config_public_reg:
|
||||||
|
abort(404)
|
||||||
|
if current_user is not None and current_user.is_authenticated:
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
if not config.get_mail_server_configured():
|
||||||
|
flash(_("Oops! Email server is not configured, please contact your administrator."), category="error")
|
||||||
|
return render_title_template('register.html', title=_("Register"), page="register")
|
||||||
if feature_support['oauth']:
|
if feature_support['oauth']:
|
||||||
register_user_with_oauth()
|
register_user_with_oauth()
|
||||||
return render_title_template('register.html', config=config, title=_("Register"), page="register")
|
return render_title_template('register.html', config=config, title=_("Register"), page="register")
|
||||||
|
|
||||||
|
|
||||||
@web.route('/login', methods=['GET', 'POST'])
|
def handle_login_user(user, remember, message, category):
|
||||||
def login():
|
login_user(user, remember=remember)
|
||||||
if current_user is not None and current_user.is_authenticated:
|
|
||||||
return redirect(url_for('web.index'))
|
|
||||||
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap:
|
|
||||||
log.error("Cannot activate LDAP authentication")
|
|
||||||
flash(_("Oops! Cannot activate LDAP authentication"), category="error")
|
|
||||||
if request.method == "POST":
|
|
||||||
form = request.form.to_dict()
|
|
||||||
user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == form['username'].strip().lower()) \
|
|
||||||
.first()
|
|
||||||
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user and form['password'] != "":
|
|
||||||
login_result, error = services.ldap.bind_user(form['username'], form['password'])
|
|
||||||
if login_result:
|
|
||||||
login_user(user, remember=bool(form.get('remember_me')))
|
|
||||||
ub.store_user_session()
|
ub.store_user_session()
|
||||||
log.debug("You are now logged in as: '{}'".format(user.name))
|
flash(message, category=category)
|
||||||
flash(_("Success! You are now logged in as: %(nickname)s", nickname=user.name),
|
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
|
||||||
category="success")
|
|
||||||
return redirect_back(url_for("web.index"))
|
return redirect_back(url_for("web.index"))
|
||||||
elif login_result is None and user and check_password_hash(str(user.password), form['password']) \
|
|
||||||
and user.name != "Guest":
|
|
||||||
login_user(user, remember=bool(form.get('remember_me')))
|
|
||||||
ub.store_user_session()
|
|
||||||
log.info("Local Fallback Login as: '{}'".format(user.name))
|
|
||||||
flash(_("Fallback Login as: %(nickname)s, LDAP Server not reachable, or user not known",
|
|
||||||
nickname=user.name),
|
|
||||||
category="warning")
|
|
||||||
return redirect_back(url_for("web.index"))
|
|
||||||
elif login_result is None:
|
|
||||||
log.info(error)
|
|
||||||
flash(_("Oops! Login Failed: %(message)s", message=error), category="error")
|
|
||||||
else:
|
|
||||||
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
|
||||||
log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ip_address)
|
|
||||||
flash(_("Oops! Invalid Username or Password."), category="error")
|
|
||||||
else:
|
|
||||||
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
|
||||||
if form.get('forgot', "") == 'forgot':
|
|
||||||
if user is not None and user.name != "Guest":
|
|
||||||
ret, __ = reset_password(user.id)
|
|
||||||
if ret == 1:
|
|
||||||
flash(_("Success! New Password was sent to your Email."), category="info")
|
|
||||||
log.info('Password reset for user "%s" IP-address: %s', form['username'], ip_address)
|
|
||||||
else:
|
|
||||||
log.error("An unknown error occurred. Please try again later")
|
|
||||||
flash(_("Oops! An unknown error occurred. Please try again later."), category="error")
|
|
||||||
else:
|
|
||||||
flash(_("Oops! Please enter a valid username to reset password"), category="error")
|
|
||||||
log.warning('Username missing for password reset IP-address: %s', ip_address)
|
|
||||||
else:
|
|
||||||
if user and check_password_hash(str(user.password), form['password']) and user.name != "Guest":
|
|
||||||
login_user(user, remember=bool(form.get('remember_me')))
|
|
||||||
ub.store_user_session()
|
|
||||||
log.debug("You are now logged in as: '%s'", user.name)
|
|
||||||
flash(_("Success! You are now logged in as: %(nickname)s", nickname=user.name), category="success")
|
|
||||||
config.config_is_initial = False
|
|
||||||
return redirect_back(url_for("web.index"))
|
|
||||||
else:
|
|
||||||
log.warning('Login failed for user "{}" IP-address: {}'.format(form['username'], ip_address))
|
|
||||||
flash(_("Oops! Invalid Username or Password."), category="error")
|
|
||||||
|
|
||||||
|
|
||||||
|
def render_login(username="", password=""):
|
||||||
next_url = request.args.get('next', default=url_for("web.index"), type=str)
|
next_url = request.args.get('next', default=url_for("web.index"), type=str)
|
||||||
if url_for("web.logout") == next_url:
|
if url_for("web.logout") == next_url:
|
||||||
next_url = url_for("web.index")
|
next_url = url_for("web.index")
|
||||||
|
@ -1355,10 +1319,91 @@ def login():
|
||||||
title=_("Login"),
|
title=_("Login"),
|
||||||
next_url=next_url,
|
next_url=next_url,
|
||||||
config=config,
|
config=config,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
oauth_check=oauth_check,
|
oauth_check=oauth_check,
|
||||||
mail=config.get_mail_server_configured(), page="login")
|
mail=config.get_mail_server_configured(), page="login")
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/login', methods=['GET'])
|
||||||
|
def login():
|
||||||
|
if current_user is not None and current_user.is_authenticated:
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap:
|
||||||
|
log.error(u"Cannot activate LDAP authentication")
|
||||||
|
flash(_(u"Cannot activate LDAP authentication"), category="error")
|
||||||
|
return render_login()
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/login', methods=['POST'])
|
||||||
|
@limiter.limit("40/day", key_func=lambda: request.form.get('username', "").strip().lower())
|
||||||
|
@limiter.limit("3/minute", key_func=lambda: request.form.get('username', "").strip().lower())
|
||||||
|
def login_post():
|
||||||
|
form = request.form.to_dict()
|
||||||
|
try:
|
||||||
|
limiter.check()
|
||||||
|
except RateLimitExceeded:
|
||||||
|
flash(_(u"Please wait one minute before next login"), category="error")
|
||||||
|
return render_login(form.get("username", ""), form.get("password", ""))
|
||||||
|
if current_user is not None and current_user.is_authenticated:
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap:
|
||||||
|
log.error(u"Cannot activate LDAP authentication")
|
||||||
|
flash(_(u"Cannot activate LDAP authentication"), category="error")
|
||||||
|
user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == form.get('username', "").strip().lower()) \
|
||||||
|
.first()
|
||||||
|
remember_me = bool(form.get('remember_me'))
|
||||||
|
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user and form['password'] != "":
|
||||||
|
login_result, error = services.ldap.bind_user(form['username'], form['password'])
|
||||||
|
if login_result:
|
||||||
|
log.debug(u"You are now logged in as: '{}'".format(user.name))
|
||||||
|
return handle_login_user(user,
|
||||||
|
remember_me,
|
||||||
|
_(u"you are now logged in as: '%(nickname)s'", nickname=user.name),
|
||||||
|
"success")
|
||||||
|
elif login_result is None and user and check_password_hash(str(user.password), form['password']) \
|
||||||
|
and user.name != "Guest":
|
||||||
|
log.info("Local Fallback Login as: '{}'".format(user.name))
|
||||||
|
return handle_login_user(user,
|
||||||
|
remember_me,
|
||||||
|
_(u"Fallback Login as: '%(nickname)s', "
|
||||||
|
u"LDAP Server not reachable, or user not known", nickname=user.name),
|
||||||
|
"warning")
|
||||||
|
elif login_result is None:
|
||||||
|
log.info(error)
|
||||||
|
flash(_(u"Could not login: %(message)s", message=error), category="error")
|
||||||
|
else:
|
||||||
|
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||||
|
log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ip_address)
|
||||||
|
flash(_(u"Wrong Username or Password"), category="error")
|
||||||
|
else:
|
||||||
|
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||||
|
if form.get('forgot', "") == 'forgot':
|
||||||
|
if user is not None and user.name != "Guest":
|
||||||
|
ret, __ = reset_password(user.id)
|
||||||
|
if ret == 1:
|
||||||
|
flash(_(u"New Password was send to your email address"), category="info")
|
||||||
|
log.info('Password reset for user "%s" IP-address: %s', form['username'], ip_address)
|
||||||
|
else:
|
||||||
|
log.error(u"An unknown error occurred. Please try again later")
|
||||||
|
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||||
|
else:
|
||||||
|
flash(_(u"Please enter valid username to reset password"), category="error")
|
||||||
|
log.warning('Username missing for password reset IP-address: %s', ip_address)
|
||||||
|
else:
|
||||||
|
if user and check_password_hash(str(user.password), form['password']) and user.name != "Guest":
|
||||||
|
config.config_is_initial = False
|
||||||
|
log.debug(u"You are now logged in as: '{}'".format(user.name))
|
||||||
|
return handle_login_user(user,
|
||||||
|
remember_me,
|
||||||
|
_(u"You are now logged in as: '%(nickname)s'", nickname=user.name),
|
||||||
|
"success")
|
||||||
|
else:
|
||||||
|
log.warning('Login failed for user "{}" IP-address: {}'.format(form['username'], ip_address))
|
||||||
|
flash(_(u"Wrong Username or Password"), category="error")
|
||||||
|
return render_login(form.get("username", ""), form.get("password", ""))
|
||||||
|
|
||||||
|
|
||||||
@web.route('/logout')
|
@web.route('/logout')
|
||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
|
@ -1375,10 +1420,10 @@ def logout():
|
||||||
def change_profile(kobo_support, local_oauth_check, oauth_status, translations, languages):
|
def change_profile(kobo_support, local_oauth_check, oauth_status, translations, languages):
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
current_user.random_books = 0
|
current_user.random_books = 0
|
||||||
if current_user.role_passwd() or current_user.role_admin():
|
|
||||||
if to_save.get("password"):
|
|
||||||
current_user.password = generate_password_hash(to_save.get("password"))
|
|
||||||
try:
|
try:
|
||||||
|
if current_user.role_passwd() or current_user.role_admin():
|
||||||
|
if to_save.get("password", "") != "":
|
||||||
|
current_user.password = generate_password_hash(to_save.get("password"))
|
||||||
if to_save.get("kindle_mail", current_user.kindle_mail) != current_user.kindle_mail:
|
if to_save.get("kindle_mail", current_user.kindle_mail) != current_user.kindle_mail:
|
||||||
current_user.kindle_mail = valid_email(to_save.get("kindle_mail"))
|
current_user.kindle_mail = valid_email(to_save.get("kindle_mail"))
|
||||||
new_email = valid_email(to_save.get("email", current_user.email))
|
new_email = valid_email(to_save.get("email", current_user.email))
|
||||||
|
@ -1405,6 +1450,7 @@ def change_profile(kobo_support, local_oauth_check, oauth_status, translations,
|
||||||
flash(str(ex), category="error")
|
flash(str(ex), category="error")
|
||||||
return render_title_template("user_edit.html",
|
return render_title_template("user_edit.html",
|
||||||
content=current_user,
|
content=current_user,
|
||||||
|
config=config,
|
||||||
translations=translations,
|
translations=translations,
|
||||||
profile=1,
|
profile=1,
|
||||||
languages=languages,
|
languages=languages,
|
||||||
|
@ -1456,6 +1502,7 @@ def profile():
|
||||||
profile=1,
|
profile=1,
|
||||||
languages=languages,
|
languages=languages,
|
||||||
content=current_user,
|
content=current_user,
|
||||||
|
config=config,
|
||||||
kobo_support=kobo_support,
|
kobo_support=kobo_support,
|
||||||
title=_("%(name)s's Profile", name=current_user.name),
|
title=_("%(name)s's Profile", name=current_user.name),
|
||||||
page="me",
|
page="me",
|
||||||
|
|
|
@ -18,3 +18,4 @@ lxml>=3.8.0,<5.0.0
|
||||||
flask-wtf>=0.14.2,<1.2.0
|
flask-wtf>=0.14.2,<1.2.0
|
||||||
chardet>=3.0.0,<4.1.0
|
chardet>=3.0.0,<4.1.0
|
||||||
advocate>=1.0.0,<1.1.0
|
advocate>=1.0.0,<1.1.0
|
||||||
|
Flask-Limiter>=2.3.0,<3.3.0
|
||||||
|
|
|
@ -58,6 +58,7 @@ install_requires =
|
||||||
flask-wtf>=0.14.2,<1.2.0
|
flask-wtf>=0.14.2,<1.2.0
|
||||||
chardet>=3.0.0,<4.1.0
|
chardet>=3.0.0,<4.1.0
|
||||||
advocate>=1.0.0,<1.1.0
|
advocate>=1.0.0,<1.1.0
|
||||||
|
Flask-Limiter>=2.3.0,<3.2.0
|
||||||
|
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user