From 83b1fbb9d67b6752a7d8bb4f8e41e3d35e56ed4d Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Sun, 14 Mar 2021 18:09:21 +0100 Subject: [PATCH] Lazy loading of KVSessionExtension - call cleanup_sessions on first kvstore access this allows to run cmdline actions without redis (and makes it faster) - Allow development using DictStore by setting REDIS_ADDRESS to the empty string in env - don't sign 64bit random session id as suggested by nextgens --- core/admin/mailu/__init__.py | 12 +++--- core/admin/mailu/utils.py | 65 +++++++++++++++++++++++++++++++- core/admin/requirements-prod.txt | 1 + 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index f9ca2466..40cc9cff 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -1,8 +1,8 @@ +""" Mailu admin app +""" + import flask import flask_bootstrap -import redis -from flask_kvsession import KVSessionExtension -from simplekv.memory.redisstore import RedisStore from mailu import utils, debug, models, manage, configuration @@ -20,7 +20,8 @@ def create_app_from_config(config): # Initialize application extensions config.init_app(app) models.db.init_app(app) - KVSessionExtension(RedisStore(redis.StrictRedis().from_url('redis://{0}/3'.format(config['REDIS_ADDRESS']))), app).cleanup_sessions(app) + utils.kvsession.init_kvstore(config) + utils.kvsession.init_app(app) utils.limiter.init_app(app) utils.babel.init_app(app) utils.login.init_app(app) @@ -53,8 +54,7 @@ def create_app_from_config(config): def create_app(): - """ Create a new application based on the config module + """ Create a new application based on the config module """ config = configuration.ConfigManager() return create_app_from_config(config) - diff --git a/core/admin/mailu/utils.py b/core/admin/mailu/utils.py index ce12a09a..852fe8ad 100644 --- a/core/admin/mailu/utils.py +++ b/core/admin/mailu/utils.py @@ -1,11 +1,18 @@ -from mailu import models, limiter +""" Mailu admin app utilities +""" + +from mailu import limiter import flask import flask_login -import flask_script import flask_migrate import flask_babel +import flask_kvsession +import redis +from simplekv.memory import DictStore +from simplekv.memory.redisstore import RedisStore +from itsdangerous.encoding import want_bytes from werkzeug.contrib import fixers @@ -33,6 +40,10 @@ def get_locale(): # Proxy fixer class PrefixMiddleware(object): + """ fix proxy headers """ + def __init__(self): + self.app = None + def __call__(self, environ, start_response): prefix = environ.get('HTTP_X_FORWARDED_PREFIX', '') if prefix: @@ -48,3 +59,53 @@ proxy = PrefixMiddleware() # Data migrate migrate = flask_migrate.Migrate() + + +# session store +class NullSigner(object): + """NullSigner does not sign nor unsign""" + def __init__(self, *args, **kwargs): + pass + def sign(self, value): + """Signs the given string.""" + return want_bytes(value) + def unsign(self, signed_value): + """Unsigns the given string.""" + return want_bytes(signed_value) + +class KVSessionIntf(flask_kvsession.KVSessionInterface): + """ KVSession interface allowing to run int function on first access """ + def __init__(self, app, init_fn=None): + if init_fn: + app.kvsession_init = init_fn + else: + self._first_run(None) + def _first_run(self, app): + if app: + app.kvsession_init() + self.open_session = super().open_session + self.save_session = super().save_session + def open_session(self, app, request): + self._first_run(app) + return super().open_session(app, request) + def save_session(self, app, session, response): + self._first_run(app) + return super().save_session(app, session, response) + +class KVSessionExt(flask_kvsession.KVSessionExtension): + """ Activates Flask-KVSession for an application. """ + def init_kvstore(self, config): + """ Initialize kvstore - fallback to DictStore without REDIS_ADDRESS """ + if addr := config.get('REDIS_ADDRESS'): + self.default_kvstore = RedisStore(redis.StrictRedis().from_url(f'redis://{addr}/3')) + else: + self.default_kvstore = DictStore() + + def init_app(self, app, session_kvstore=None): + """ Initialize application and KVSession. """ + super().init_app(app, session_kvstore) + app.session_interface = KVSessionIntf(app, self.cleanup_sessions) + +kvsession = KVSessionExt() + +flask_kvsession.Signer = NullSigner diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index 54cf9a14..cd084684 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -39,6 +39,7 @@ python-editor==1.0.4 pytz==2019.1 PyYAML==5.1 redis==3.2.1 +simplekv==0.14.1 #alpine3:12 provides six==1.15.0 #six==1.12.0 socrate==0.1.1