1
0
mirror of https://github.com/Mailu/Mailu.git synced 2024-12-12 10:45:38 +02:00

Tweak sessions

simplify:
- make all sessions permanent by default
- update the TTL of sessions on access (save always)
- fix session-expiry, modulo 8byte precision
This commit is contained in:
Florent Daigniere 2021-12-19 20:52:51 +01:00
parent ea96a68eb4
commit 02c93c44f2
3 changed files with 18 additions and 40 deletions

View File

@ -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])

View File

@ -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

View File

@ -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.