diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py index b60b8a3e..72241d0c 100644 --- a/core/admin/mailu/configuration.py +++ b/core/admin/mailu/configuration.py @@ -70,7 +70,8 @@ DEFAULT_CONFIG = { # Advanced settings 'LOG_LEVEL': 'WARNING', 'SESSION_KEY_BITS': 128, - 'SESSION_LIFETIME': 24, + 'SESSION_TIMEOUT': 3600, + 'PERMANENT_SESSION_LIFETIME': 30*24*3600, 'SESSION_COOKIE_SECURE': True, 'CREDENTIAL_ROUNDS': 12, 'TZ': 'Etc/UTC', @@ -152,7 +153,7 @@ class ConfigManager: self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3' self.config['SESSION_COOKIE_SAMESITE'] = 'Strict' self.config['SESSION_COOKIE_HTTPONLY'] = True - self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME'])) + self.config['SESSION_PERMANENT'] = True hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')] self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr) self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s]) diff --git a/core/admin/mailu/utils.py b/core/admin/mailu/utils.py index 9c56d0be..e12def11 100644 --- a/core/admin/mailu/utils.py +++ b/core/admin/mailu/utils.py @@ -28,6 +28,7 @@ import flask_babel import ipaddress import redis +from datetime import datetime, timedelta from flask.sessions import SessionMixin, SessionInterface from itsdangerous.encoding import want_bytes from werkzeug.datastructures import CallbackDict @@ -125,8 +126,6 @@ migrate = flask_migrate.Migrate() class RedisStore: """ Stores session data in a redis db. """ - has_ttl = True - def __init__(self, redisstore): self.redis = redisstore @@ -157,8 +156,6 @@ class RedisStore: class DictStore: """ Stores session data in a python dict. """ - has_ttl = False - def __init__(self): self.dict = {} @@ -166,7 +163,7 @@ class DictStore: """ load item from store. """ return self.dict[key] - def put(self, key, value, ttl_secs=None): + def put(self, key, value, ttl=None): """ save item to store. """ self.dict[key] = value @@ -284,14 +281,11 @@ class MailuSession(CallbackDict, SessionMixin): if key != self._key: self.delete() - # remember time to refresh - self['_refresh'] = int(time.time()) + self.app.permanent_session_lifetime.total_seconds()/2 - # save session self.app.session_store.put( key, pickle.dumps(dict(self)), - self.app.permanent_session_lifetime.total_seconds() + int(app.config['SESSION_TIMEOUT']), ) self._key = key @@ -301,11 +295,6 @@ class MailuSession(CallbackDict, SessionMixin): return set_cookie - def needs_refresh(self): - """ Checks if server side session needs to be refreshed. """ - - return int(time.time()) > self.get('_refresh', 0) - class MailuSessionConfig: """ Stores sessions crypto config """ @@ -350,7 +339,7 @@ class MailuSessionConfig: """ Generate base64 representation of creation time. """ return self._encode(int(now or time.time()).to_bytes(8, byteorder='big').lstrip(b'\0')) - def parse_key(self, key, app=None, validate=False, now=None): + def parse_key(self, key, app=None, now=None): """ Split key into sid, uid and creation time. """ if not (isinstance(key, bytes) and self._key_min <= len(key) <= self._key_max): @@ -365,13 +354,12 @@ class MailuSessionConfig: if created is None or self._decode(uid) is None or self._decode(sid) is None: return None - # validate creation time when requested or store does not support ttl - if validate or not app.session_store.has_ttl: - if now is None: - now = int(time.time()) - created = int.from_bytes(created, byteorder='big') - if not created < now < created + app.permanent_session_lifetime.total_seconds(): - return None + # validate creation time + if now is None: + now = int(time.time()) + created = int.from_bytes(created, byteorder='big') + if not created <= now <= created + app.config['PERMANENT_SESSION_LIFETIME']: + return None return (uid, sid, crt) @@ -410,23 +398,12 @@ class MailuSessionInterface(SessionInterface): if session.accessed: response.vary.add('Cookie') - set_cookie = session.permanent and app.config['SESSION_REFRESH_EACH_REQUEST'] - need_refresh = session.needs_refresh() - - # save modified session or refresh unmodified session - if session.modified or need_refresh: - set_cookie |= session.save() - - # set cookie on refreshed permanent sessions - if need_refresh and session.permanent: - set_cookie = True - - # set or update cookie if necessary - if set_cookie: + # save session and update cookie if necessary + if session.save(): response.set_cookie( app.session_cookie_name, session.sid, - expires=self.get_expiration_time(app, session), + expires=datetime.now()+timedelta(seconds=int(app.config['PERMANENT_SESSION_LIFETIME'])), httponly=self.get_cookie_httponly(app), domain=self.get_cookie_domain(app), path=self.get_cookie_path(app), @@ -446,7 +423,7 @@ class MailuSessionExtension: count = 0 for key in app.session_store.list(): - if not app.session_config.parse_key(key, app, validate=True, now=now): + if not app.session_config.parse_key(key, app, now=now): app.session_store.delete(key) count += 1 diff --git a/docs/configuration.rst b/docs/configuration.rst index 6f4ba563..75544c01 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -181,7 +181,7 @@ The ``CREDENTIAL_ROUNDS`` (default: 12) setting is the number of rounds used by The ``SESSION_COOKIE_SECURE`` (default: True) setting controls the secure flag on the cookies of the administrative interface. It should only be turned off if you intend to access it over plain HTTP. -``SESSION_LIFETIME`` (default: 24) is the length in hours a session is valid for on the administrative interface. +``SESSION_TIMEOUT`` (default: 3600) is the maximum amount of time in seconds between requests before a session is invalidated. ``PERMANENT_SESSION_LIFETIME`` (default: 108000) is the maximum amount of time in seconds a session can be kept alive for if it hasn't timed-out. The ``LOG_LEVEL`` setting is used by the python start-up scripts as a logging threshold. Log messages equal or higher than this priority will be printed.