video2commons/frontend/redisession.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# Adapted from: http://flask.pocoo.org/snippets/75/ under Public Domain
#
"""Server side session on Redis."""
import json
from datetime import timedelta
from uuid import uuid4
from redis import Redis
from flask import url_for
from flask.sessions import SessionInterface, SessionMixin
class RedisSession(dict, SessionMixin):
"""Redis Session class."""
def __init__(self, initial=None, sid=None, new=False):
"""Initialize the instance."""
self.sid = sid
self.new = new
initial = initial or {}
if initial:
self.update(initial or {})
self._initials = json.dumps(dict(self))
@property
def modified(self):
"""Check if this is modified recursively."""
return json.dumps(dict(self)) != self._initials
def rollback(self):
"""Rollback all changes."""
self.clear()
self.update(json.loads(self._initials))
class RedisSessionInterface(SessionInterface):
"""Redis Session interface: provides methods dealing with sessions."""
serializer = json
session_class = RedisSession
def __init__(self, redis=None, prefix='session:'):
"""Initialize the instance."""
if redis is None:
redis = Redis()
self.redis = redis
self.prefix = prefix
def generate_sid(self):
"""Generate a session ID."""
return str(uuid4())
def get_redis_expiration_time(self, app, session):
"""Get Redis expiration time."""
if session.permanent:
return app.permanent_session_lifetime
return timedelta(days=1)
def open_session(self, app, request):
"""Get session from Redis / start a new session."""
sid = request.cookies.get(app.session_cookie_name)
if sid:
val = self.redis.get(self.prefix + sid)
if val is not None:
try:
data = self.serializer.loads(val)
except ValueError:
pass
else:
return self.session_class(data, sid=sid)
# SECURITY: If the session id is invalid, we create a new one, to
# prevent cookie-injection.
# https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-zheng.pdf
sid = self.generate_sid()
return self.session_class(sid=sid, new=True)
def save_session(self, app, session, response):
"""Save session to Redis."""
domain = self.get_cookie_domain(app)
path = url_for('main', _external=False)
if not session:
self.redis.delete(self.prefix + session.sid)
if session.modified:
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
else:
redis_exp = self.get_redis_expiration_time(app, session)
cookie_exp = self.get_expiration_time(app, session)
if session.modified:
val = self.serializer.dumps(dict(session))
self.redis.setex(self.prefix + session.sid,
int(redis_exp.total_seconds()), val)
else:
self.redis.expire(self.prefix + session.sid,
int(redis_exp.total_seconds()))
response.set_cookie(app.session_cookie_name, session.sid,
expires=cookie_exp, httponly=True,
domain=domain, path=path, secure=True)
def abandon_session(self, app, session):
"""Delete the session from redis, empty it, and reinit."""
session.clear()
if not session.new:
self.redis.delete(self.prefix + session.sid)
sid = self.generate_sid()
session._get_current_object().__init__(sid=sid, new=True)