Save sessions in the cache

This commit is contained in:
Gergely Polonkai 2018-07-18 08:26:39 +02:00
parent 6c98c9d7ca
commit 8d71edae5e
3 changed files with 137 additions and 2 deletions

View File

@ -26,7 +26,7 @@ from flask_security import SQLAlchemyUserDatastore, current_user, login_required
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from calsocial.account import AccountBlueprint
from calsocial.cache import cache
from calsocial.cache import CachedSessionInterface, cache
from calsocial.utils import RoutedMixin
@ -68,6 +68,8 @@ class CalendarSocialApp(Flask, RoutedMixin):
Flask.__init__(self, name)
self.session_interface = CachedSessionInterface()
self._timezone = None
config_name = os.environ.get('ENV', config or 'dev')

View File

@ -17,6 +17,137 @@
"""Caching functionality for Calendar.social
"""
from datetime import timedelta
import pickle
from uuid import uuid4
from flask import current_app, has_request_context, request, session
from flask.sessions import SessionInterface, SessionMixin
from flask_caching import Cache
from werkzeug.datastructures import CallbackDict
cache = Cache() # pylint: disable=invalid-name
class CachedSession(CallbackDict, SessionMixin): # pylint: disable=too-many-ancestors
"""Object for session data saved in the cache
"""
def __init__(self, initial=None, sid=None, new=False):
self.__modifying = False
def on_update(self):
"""Function to call when session data is updated
"""
if self.__modifying:
return
self.__modifying = True
if has_request_context():
self['ip'] = request.remote_addr
self.modified = True
self.__modifying = False
CallbackDict.__init__(self, initial, on_update)
self.sid = sid
self.new = new
self.modified = False
@property
def user(self):
from calsocial.models import User
if 'user_id' not in self:
return None
return User.query.get(self['user_id'])
class CachedSessionInterface(SessionInterface):
"""A session interface that loads/saves session data from the cache
"""
serializer = pickle
session_class = CachedSession
global_cache = cache
def __init__(self, prefix='session:'):
self.cache = cache
self.prefix = prefix
@staticmethod
def generate_sid():
"""Generade a new session ID
"""
return str(uuid4())
@staticmethod
def get_cache_expiration_time(app, session):
"""Get the expiration time of the cache entry
"""
if session.permanent:
return app.permanent_session_lifetime
return timedelta(days=1)
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self.generate_sid()
return self.session_class(sid=sid, new=True)
session = self.load_session(sid)
if session is None:
return self.session_class(sid=sid, new=True)
return session
def load_session(self, sid):
"""Load a specific session from the cache
"""
val = self.cache.get(self.prefix + sid)
if val is None:
return None
data = self.serializer.loads(val)
return self.session_class(data, sid=sid)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
self.cache.delete(self.prefix + session.sid)
if session.modified:
response.delete_cookie(app.session_cookie_name, domain=domain)
return
cache_exp = self.get_cache_expiration_time(app, session)
cookie_exp = self.get_expiration_time(app, session)
val = self.serializer.dumps(dict(session))
self.cache.set(self.prefix + session.sid, val, int(cache_exp.total_seconds()))
response.set_cookie(app.session_cookie_name,
session.sid,
expires=cookie_exp,
httponly=True,
domain=domain)
def delete_session(self, sid):
if has_request_context() and session.sid == sid:
raise ValueError('Will not delete the current session')
cache.delete(self.prefix + sid)

View File

@ -17,7 +17,7 @@
"""Security related things for Calendar.social
"""
from flask import current_app
from flask import current_app, session
from flask_login.signals import user_logged_in, user_logged_out
from flask_security import Security, AnonymousUser as BaseAnonymousUser
@ -45,6 +45,8 @@ def login_handler(app, user): # pylint: disable=unused-argument
AuditLog.log(user, AuditLog.TYPE_LOGIN_SUCCESS)
user.active_sessions += [session.sid]
@user_logged_out.connect
def logout_handler(app, user): # pylint: disable=unused-argument