session_db : store sessions in a database rather than in filestore

pull/2441/head
Nicolas Seinlet 2017-10-03 14:17:19 +02:00 committed by Stéphane Bidoul
parent 26616f2155
commit 5cc37f95b1
4 changed files with 151 additions and 0 deletions

View File

@ -0,0 +1 @@
import models

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
{
'name': "Store sessions in DB",
'description': """
Storing sessions in DB
- workls only with workers > 0
- set the session_db parameter in the odoo config file
- session_db parameter value is a full postgresql connection string, like user:passwd@server/db
- choose another DB than the odoo db itself, for security purpose
- it also possible to use another PostgreSQL user for the same security reasons
Set this module in the server wide modules
""",
'category': '',
'version': '1.0',
'depends': [
],
'data': [
],
'demo': [
],
}

View File

@ -0,0 +1 @@
import session

View File

@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
import psycopg2
import json
import logging
import random
import werkzeug.contrib.sessions
import time
import odoo
from odoo import http
from odoo.tools.func import lazy_property
_logger = logging.getLogger(__name__)
def with_cursor(func):
def wrapper(self, *args, **kwargs):
tries = 0
while True:
tries += 1
try:
return func(self, *args, **kwargs)
except psycopg2.InterfaceError as e:
_logger.info("Session in DB connection Retry %s/5" % tries)
if tries>4:
raise e
self._open_connection()
return wrapper
class PGSessionStore(werkzeug.contrib.sessions.SessionStore):
# FIXME This class is NOT thread-safe. Only use in worker mode
def __init__(self, uri, session_class=None):
super(PGSessionStore, self).__init__(session_class)
self._uri = uri
self._open_connection()
self._setup_db()
def __del__(self):
self._cr.close()
def _open_connection(self):
cnx = odoo.sql_db.db_connect(self._uri, allow_uri=True)
self._cr = cnx.cursor()
self._cr.autocommit(True)
@with_cursor
def _setup_db(self):
self._cr.execute("""
CREATE TABLE IF NOT EXISTS http_sessions (
sid varchar PRIMARY KEY,
write_date timestamp without time zone NOT NULL,
payload text NOT NULL
)
""")
@with_cursor
def save(self, session):
payload = json.dumps(dict(session))
self._cr.execute("""
INSERT INTO http_sessions(sid, write_date, payload)
VALUES (%(sid)s, now() at time zone 'UTC', %(payload)s)
ON CONFLICT (sid)
DO UPDATE SET payload = %(payload)s,
write_date = now() at time zone 'UTC'
""", dict(sid=session.sid, payload=payload))
@with_cursor
def delete(self, session):
self._cr.execute("DELETE FROM http_sessions WHERE sid=%s", [session.sid])
@with_cursor
def get(self, sid):
self._cr.execute("UPDATE http_sessions SET write_date = now() at time zone 'UTC' WHERE sid=%s", [sid])
self._cr.execute("SELECT payload FROM http_sessions WHERE sid=%s", [sid])
try:
data = json.loads(self._cr.fetchone()[0])
except Exception:
return self.new()
return self.session_class(data, sid, False)
@with_cursor
def gc(self):
self._cr.execute(
"DELETE FROM http_sessions WHERE now() at time zone 'UTC' - write_date > '7 days'"
)
def session_gc(session_store):
"""
Global cleaning of sessions using either the standard way (delete session files),
Or the DB way.
"""
if random.random() < 0.001:
# we keep session one week
if hasattr(session_store, 'gc'):
session_store.gc()
return
last_week = time.time() - 60*60*24*7
for fname in os.listdir(session_store.path):
path = os.path.join(session_store.path, fname)
try:
if os.path.getmtime(path) < last_week:
os.unlink(path)
except OSError:
pass
class Root(http.Root):
@lazy_property
def session_store(self):
"""
Store sessions in DB rather than on FS if parameter permit so
"""
# Setup http sessions
session_db = odoo.tools.config.get('session_db')
if session_db:
_logger.debug("Sessions in db %s" % session_db)
return PGSessionStore(session_db, session_class=http.OpenERPSession)
path = odoo.tools.config.session_dir
_logger.debug('HTTP sessions stored in: %s', path)
return werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=http.OpenERPSession)
# #Monkey patch of standard methods
_logger.debug("Monkey patching sessions")
http.session_gc = session_gc
http.root = Root()