# Copyright 2016-2019 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import json import logging from odoo.service import security from odoo.tools._vendor.sessions import SessionStore from . import json_encoding # this is equal to the duration of the session garbage collector in # odoo.http.session_gc() DEFAULT_SESSION_TIMEOUT = 60 * 60 * 24 * 7 # 7 days in seconds DEFAULT_SESSION_TIMEOUT_ANONYMOUS = 60 * 60 * 3 # 3 hours in seconds _logger = logging.getLogger(__name__) class RedisSessionStore(SessionStore): """SessionStore that saves session to redis""" def __init__( self, redis, session_class=None, prefix="", expiration=None, anon_expiration=None, ): super().__init__(session_class=session_class) self.redis = redis if expiration is None: self.expiration = DEFAULT_SESSION_TIMEOUT else: self.expiration = expiration if anon_expiration is None: self.anon_expiration = DEFAULT_SESSION_TIMEOUT_ANONYMOUS else: self.anon_expiration = anon_expiration self.prefix = "session:" if prefix: self.prefix = "%s:%s:" % (self.prefix, prefix) def build_key(self, sid): return "%s%s" % (self.prefix, sid) def save(self, session): key = self.build_key(session.sid) # allow to set a custom expiration for a session # such as a very short one for monitoring requests if session.uid: expiration = session.expiration or self.expiration else: expiration = session.expiration or self.anon_expiration if _logger.isEnabledFor(logging.DEBUG): if session.uid: user_msg = "user '%s' (id: %s)" % (session.login, session.uid) else: user_msg = "anonymous user" _logger.debug( "saving session with key '%s' and " "expiration of %s seconds for %s", key, expiration, user_msg, ) data = json.dumps(dict(session), cls=json_encoding.SessionEncoder).encode( "utf-8" ) if self.redis.set(key, data): return self.redis.expire(key, expiration) def delete(self, session): key = self.build_key(session.sid) _logger.debug("deleting session with key %s", key) return self.redis.delete(key) def get(self, sid): if not self.is_valid_key(sid): _logger.debug( "session with invalid sid '%s' has been asked, " "returning a new one", sid, ) return self.new() key = self.build_key(sid) saved = self.redis.get(key) if not saved: _logger.debug( "session with non-existent key '%s' has been asked, " "returning a new one", key, ) return self.new() try: data = json.loads(saved.decode("utf-8"), cls=json_encoding.SessionDecoder) except ValueError: _logger.debug( "session for key '%s' has been asked but its json " "content could not be read, it has been reset", key, ) data = {} return self.session_class(data, sid, False) def list(self): keys = self.redis.keys("%s*" % self.prefix) _logger.debug("a listing redis keys has been called") return [key[len(self.prefix) :] for key in keys] def rotate(self, session, env): self.delete(session) session.sid = self.generate_key() if session.uid and env: session.session_token = security.compute_session_token(session, env) self.save(session) def vacuum(self): """Do not garbage collect the sessions Redis keys are automatically cleaned at the end of their expiration. """ return None