forked from gergely/calendar-social
Save sessions in the cache
This commit is contained in:
parent
6c98c9d7ca
commit
8d71edae5e
@ -26,7 +26,7 @@ from flask_security import SQLAlchemyUserDatastore, current_user, login_required
|
|||||||
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
|
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
|
||||||
|
|
||||||
from calsocial.account import AccountBlueprint
|
from calsocial.account import AccountBlueprint
|
||||||
from calsocial.cache import cache
|
from calsocial.cache import CachedSessionInterface, cache
|
||||||
from calsocial.utils import RoutedMixin
|
from calsocial.utils import RoutedMixin
|
||||||
|
|
||||||
|
|
||||||
@ -68,6 +68,8 @@ class CalendarSocialApp(Flask, RoutedMixin):
|
|||||||
|
|
||||||
Flask.__init__(self, name)
|
Flask.__init__(self, name)
|
||||||
|
|
||||||
|
self.session_interface = CachedSessionInterface()
|
||||||
|
|
||||||
self._timezone = None
|
self._timezone = None
|
||||||
|
|
||||||
config_name = os.environ.get('ENV', config or 'dev')
|
config_name = os.environ.get('ENV', config or 'dev')
|
||||||
|
@ -17,6 +17,137 @@
|
|||||||
"""Caching functionality for Calendar.social
|
"""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 flask_caching import Cache
|
||||||
|
from werkzeug.datastructures import CallbackDict
|
||||||
|
|
||||||
cache = Cache() # pylint: disable=invalid-name
|
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)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"""Security related things for Calendar.social
|
"""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_login.signals import user_logged_in, user_logged_out
|
||||||
from flask_security import Security, AnonymousUser as BaseAnonymousUser
|
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)
|
AuditLog.log(user, AuditLog.TYPE_LOGIN_SUCCESS)
|
||||||
|
|
||||||
|
user.active_sessions += [session.sid]
|
||||||
|
|
||||||
|
|
||||||
@user_logged_out.connect
|
@user_logged_out.connect
|
||||||
def logout_handler(app, user): # pylint: disable=unused-argument
|
def logout_handler(app, user): # pylint: disable=unused-argument
|
||||||
|
Loading…
Reference in New Issue
Block a user