Tweak sessions

simplify:
- make all sessions permanent by default
- update the TTL of sessions on access (save always)
- fix session-expiry, modulo 8byte precision
master
Florent Daigniere 3 years ago
parent ea96a68eb4
commit 02c93c44f2

@ -70,7 +70,8 @@ DEFAULT_CONFIG = {
# Advanced settings # Advanced settings
'LOG_LEVEL': 'WARNING', 'LOG_LEVEL': 'WARNING',
'SESSION_KEY_BITS': 128, 'SESSION_KEY_BITS': 128,
'SESSION_LIFETIME': 24, 'SESSION_TIMEOUT': 3600,
'PERMANENT_SESSION_LIFETIME': 30*24*3600,
'SESSION_COOKIE_SECURE': True, 'SESSION_COOKIE_SECURE': True,
'CREDENTIAL_ROUNDS': 12, 'CREDENTIAL_ROUNDS': 12,
'TZ': 'Etc/UTC', 'TZ': 'Etc/UTC',
@ -152,7 +153,7 @@ class ConfigManager:
self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3' self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict' self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
self.config['SESSION_COOKIE_HTTPONLY'] = True 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(',')] 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['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]) self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])

@ -28,6 +28,7 @@ import flask_babel
import ipaddress import ipaddress
import redis import redis
from datetime import datetime, timedelta
from flask.sessions import SessionMixin, SessionInterface from flask.sessions import SessionMixin, SessionInterface
from itsdangerous.encoding import want_bytes from itsdangerous.encoding import want_bytes
from werkzeug.datastructures import CallbackDict from werkzeug.datastructures import CallbackDict
@ -125,8 +126,6 @@ migrate = flask_migrate.Migrate()
class RedisStore: class RedisStore:
""" Stores session data in a redis db. """ """ Stores session data in a redis db. """
has_ttl = True
def __init__(self, redisstore): def __init__(self, redisstore):
self.redis = redisstore self.redis = redisstore
@ -157,8 +156,6 @@ class RedisStore:
class DictStore: class DictStore:
""" Stores session data in a python dict. """ """ Stores session data in a python dict. """
has_ttl = False
def __init__(self): def __init__(self):
self.dict = {} self.dict = {}
@ -166,7 +163,7 @@ class DictStore:
""" load item from store. """ """ load item from store. """
return self.dict[key] return self.dict[key]
def put(self, key, value, ttl_secs=None): def put(self, key, value, ttl=None):
""" save item to store. """ """ save item to store. """
self.dict[key] = value self.dict[key] = value
@ -284,14 +281,11 @@ class MailuSession(CallbackDict, SessionMixin):
if key != self._key: if key != self._key:
self.delete() self.delete()
# remember time to refresh
self['_refresh'] = int(time.time()) + self.app.permanent_session_lifetime.total_seconds()/2
# save session # save session
self.app.session_store.put( self.app.session_store.put(
key, key,
pickle.dumps(dict(self)), pickle.dumps(dict(self)),
self.app.permanent_session_lifetime.total_seconds() int(app.config['SESSION_TIMEOUT']),
) )
self._key = key self._key = key
@ -301,11 +295,6 @@ class MailuSession(CallbackDict, SessionMixin):
return set_cookie 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: class MailuSessionConfig:
""" Stores sessions crypto config """ """ Stores sessions crypto config """
@ -350,7 +339,7 @@ class MailuSessionConfig:
""" Generate base64 representation of creation time. """ """ Generate base64 representation of creation time. """
return self._encode(int(now or time.time()).to_bytes(8, byteorder='big').lstrip(b'\0')) 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. """ """ Split key into sid, uid and creation time. """
if not (isinstance(key, bytes) and self._key_min <= len(key) <= self._key_max): 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: if created is None or self._decode(uid) is None or self._decode(sid) is None:
return None return None
# validate creation time when requested or store does not support ttl # validate creation time
if validate or not app.session_store.has_ttl: if now is None:
if now is None: now = int(time.time())
now = int(time.time()) created = int.from_bytes(created, byteorder='big')
created = int.from_bytes(created, byteorder='big') if not created <= now <= created + app.config['PERMANENT_SESSION_LIFETIME']:
if not created < now < created + app.permanent_session_lifetime.total_seconds(): return None
return None
return (uid, sid, crt) return (uid, sid, crt)
@ -410,23 +398,12 @@ class MailuSessionInterface(SessionInterface):
if session.accessed: if session.accessed:
response.vary.add('Cookie') response.vary.add('Cookie')
set_cookie = session.permanent and app.config['SESSION_REFRESH_EACH_REQUEST'] # save session and update cookie if necessary
need_refresh = session.needs_refresh() if session.save():
# 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:
response.set_cookie( response.set_cookie(
app.session_cookie_name, app.session_cookie_name,
session.sid, 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), httponly=self.get_cookie_httponly(app),
domain=self.get_cookie_domain(app), domain=self.get_cookie_domain(app),
path=self.get_cookie_path(app), path=self.get_cookie_path(app),
@ -446,7 +423,7 @@ class MailuSessionExtension:
count = 0 count = 0
for key in app.session_store.list(): 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) app.session_store.delete(key)
count += 1 count += 1

@ -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. 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. 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. Log messages equal or higher than this priority will be printed.

Loading…
Cancel
Save