From a63baa175871b416a7f0baeea86f5bdc9bc440a0 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Fri, 30 Jul 2021 11:43:26 +0200 Subject: [PATCH] better logged in session protection --- cps/admin.py | 4 + cps/ub.py | 45 +- cps/usermanagement.py | 4 +- cps/web.py | 5 +- test/Calibre-Web TestSummary_Linux.html | 2557 ++++------------------- 5 files changed, 492 insertions(+), 2123 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index cd30f7d4b..2602dffbc 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -34,6 +34,7 @@ from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response from flask_login import login_required, current_user, logout_user, confirm_login from flask_babel import gettext as _ +from flask import session as flask_session from sqlalchemy import and_ from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError @@ -98,8 +99,11 @@ def inner(*args, **kwargs): @admi.before_app_request def before_request(): + # make remember me function work if current_user.is_authenticated: confirm_login() + if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path: + logout_user() g.constants = constants g.user = current_user g.allow_registration = config.config_public_reg diff --git a/cps/ub.py b/cps/ub.py index c334ff59b..9b187639a 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -27,6 +27,8 @@ from binascii import hexlify from flask_login import AnonymousUserMixin, current_user +from flask_login import user_logged_in +from contextlib import contextmanager try: from flask_dance.consumer.backend.sqla import OAuthConsumerMixin @@ -61,6 +63,36 @@ searched_ids = {} +def signal_store_user_session(object, user): + store_user_session() + +def store_user_session(): + if flask_session.get('_user_id', ""): + try: + if not check_user_session(flask_session.get('_user_id', ""), flask_session.get('_id', "")): + user_session = User_Sessions(flask_session.get('_user_id', ""), flask_session.get('_id', "")) + session.add(user_session) + session.commit() + except (exc.OperationalError, exc.InvalidRequestError): + session.rollback() + # log.debug(flask_session.get('_id', "")) + +def delete_user_session(user_id, session_key): + try: + # log.debug(session_key) + session.query(User_Sessions).filter(User_Sessions.user_id==user_id, + User_Sessions.session_key==session_key).delete() + session.commit() + except (exc.OperationalError, exc.InvalidRequestError): + session.rollback() + + +def check_user_session(user_id, session_key): + return bool(session.query(User_Sessions).filter(User_Sessions.user_id==user_id, + User_Sessions.session_key==session_key).one_or_none()) + +user_logged_in.connect(signal_store_user_session) + def store_ids(result): ids = list() for element in result: @@ -72,7 +104,7 @@ class UserBase: @property def is_authenticated(self): - return True + return self.is_active def _has_role(self, role_flag): return constants.has_flag(self.role, role_flag) @@ -261,6 +293,17 @@ def set_view_property(self, page, prop, value): flask_session['view'][page][prop] = value return None +class User_Sessions(Base): + __tablename__ = 'user_session' + + id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey('user.id')) + session_key = Column(String, default="") + + def __init__(self, user_id, session_key): + self.user_id = user_id + self.session_key = session_key + # Baseclass representing Shelfs in calibre-web in app.db class Shelf(Base): diff --git a/cps/usermanagement.py b/cps/usermanagement.py index 78e80afe2..b18cc673b 100644 --- a/cps/usermanagement.py +++ b/cps/usermanagement.py @@ -21,7 +21,8 @@ from sqlalchemy.sql.expression import func from werkzeug.security import check_password_hash -from flask_login import login_required +from flask_login import login_required, login_user + from . import lm, ub, config, constants, services @@ -58,6 +59,7 @@ def load_user_from_request(request): if rp_header_username: user = _fetch_user_by_name(rp_header_username) if user: + login_user(user) return user auth_header = request.headers.get("Authorization") diff --git a/cps/web.py b/cps/web.py index 3176feb53..ab9c41340 100644 --- a/cps/web.py +++ b/cps/web.py @@ -1523,7 +1523,6 @@ def login(): 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() log.debug(u"You are now logged in as: '%s'", user.name) flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.name), category="success") @@ -1531,7 +1530,6 @@ def login(): 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: '%s'", user.name) flash(_(u"Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known", nickname=user.name), @@ -1561,7 +1559,6 @@ def login(): 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(u"You are now logged in as: '%s'", user.name) flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.name), category="success") config.config_is_initial = False @@ -1585,7 +1582,7 @@ def login(): @login_required def logout(): if current_user is not None and current_user.is_authenticated: - # ub.delete_user_session(current_user.id, flask_session.get('_id',"")) + ub.delete_user_session(current_user.id, flask_session.get('_id',"")) logout_user() if feature_support['oauth'] and (config.config_login_type == 2 or config.config_login_type == 3): logout_oauth_user() diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index f9a9218aa..101769f70 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@

Calibre-Web Tests

-

Start Time: 2021-07-29 20:37:20

+

Start Time: 2021-07-30 11:44:06

-

Stop Time: 2021-07-30 00:10:27

+

Stop Time: 2021-07-30 15:14:26

-

Duration: 2h 47 min

+

Duration: 2h 45 min

@@ -234,12 +234,12 @@

Calibre-Web Tests

- + TestCli 8 - 4 - 3 - 1 + 8 + 0 + 0 0 Detail @@ -266,68 +266,20 @@

Calibre-Web Tests

- +
TestCli - test_change_password
- -
- FAIL -
- - - - + PASS - +
TestCli - test_cli_SSL_files
- -
- FAIL -
- - - - + PASS @@ -341,41 +293,11 @@

Calibre-Web Tests

- +
TestCli - test_cli_different_settings_database
- -
- ERROR -
- - - - + PASS @@ -389,50 +311,22 @@

Calibre-Web Tests

- +
TestCli - test_settingsdb_not_writeable
- -
- FAIL -
- - - - + PASS - + TestCliGdrivedb 2 + 2 + 0 0 - 1 - 1 0 Detail @@ -441,72 +335,20 @@

Calibre-Web Tests

- +
TestCliGdrivedb - test_cli_gdrive_location
- -
- FAIL -
- - - - + PASS - +
TestCliGdrivedb - test_gdrive_db_nonwrite
- -
- ERROR -
- - - - + PASS @@ -560,11 +402,11 @@

Calibre-Web Tests

- + TestEbookConvertCalibre 11 - 9 - 2 + 11 + 0 0 0 @@ -619,33 +461,11 @@

Calibre-Web Tests

- +
TestEbookConvertCalibre - test_convert_wrong_excecutable
- -
- FAIL -
- - - - + PASS @@ -659,33 +479,11 @@

Calibre-Web Tests

- +
TestEbookConvertCalibre - test_email_only
- -
- FAIL -
- - - - + PASS @@ -871,12 +669,12 @@

Calibre-Web Tests

- + TestEditAdditionalBooks 13 - 8 + 12 + 0 0 - 4 1 Detail @@ -957,159 +755,29 @@

Calibre-Web Tests

- +
TestEditAdditionalBooks - test_upload_edit_role
- -
- ERROR -
- - - - + PASS - +
TestEditAdditionalBooks - test_upload_metadata_cbr
- -
- ERROR -
- - - - + PASS - +
TestEditAdditionalBooks - test_upload_metadata_cbt
- -
- ERROR -
- - - - + PASS @@ -1140,111 +808,31 @@

Calibre-Web Tests

- +
TestEditAdditionalBooks - test_writeonly_path
- -
- ERROR -
- - - - - - - - - - - _ErrorHolder - 1 - 0 - 0 - 1 - 0 - - Detail - - - - - - - -
tearDownClass (test_edit_additional_books)
- - -
- ERROR -
- - - - + PASS - + TestEditBooks 35 - 25 + 34 + 0 0 - 9 1 - Detail + Detail - +
TestEditBooks - test_download_book
@@ -1253,7 +841,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_author
@@ -1262,7 +850,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_category
@@ -1271,7 +859,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_comments
@@ -1280,7 +868,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_bool
@@ -1289,7 +877,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_categories
@@ -1298,7 +886,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_comment
@@ -1307,7 +895,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_date
@@ -1316,7 +904,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_float
@@ -1325,7 +913,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_int
@@ -1334,7 +922,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_rating
@@ -1343,7 +931,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_single_select
@@ -1352,7 +940,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_custom_text
@@ -1361,7 +949,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_language
@@ -1370,7 +958,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_publisher
@@ -1379,7 +967,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_publishing_date
@@ -1388,7 +976,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_rating
@@ -1397,7 +985,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_series
@@ -1406,7 +994,7 @@

Calibre-Web Tests

- +
TestEditBooks - test_edit_title
@@ -1415,19 +1003,19 @@

Calibre-Web Tests

- +
TestEditBooks - test_rename_uppercase_lowercase
- SKIP + SKIP
-