You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

140 lines
4.9 KiB

  1. ###################################################################################
  2. #
  3. # Copyright (c) 2017-2019 MuK IT GmbH.
  4. #
  5. # This file is part of MuK Session Store
  6. # (see https://mukit.at).
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Lesser General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ###################################################################################
  22. import json
  23. import logging
  24. import psycopg2
  25. import functools
  26. from contextlib import closing
  27. from contextlib import contextmanager
  28. from datetime import datetime, date
  29. from werkzeug.contrib.sessions import SessionStore
  30. from odoo.sql_db import db_connect
  31. from odoo.tools import config
  32. _logger = logging.getLogger(__name__)
  33. def retry_database(func):
  34. @functools.wraps(func)
  35. def wrapper(self, *args, **kwargs):
  36. for attempts in range(1, 6):
  37. try:
  38. return func(self, *args, **kwargs)
  39. except psycopg2.InterfaceError as error:
  40. _logger.warn("SessionStore connection failed! (%s/5)" % attempts)
  41. if attempts >= 5:
  42. raise error
  43. return wrapper
  44. class PostgresSessionStore(SessionStore):
  45. def __init__(self, *args, **kwargs):
  46. super(PostgresSessionStore, self).__init__(*args, **kwargs)
  47. self.dbname = config.get('session_store_dbname', 'session_store')
  48. self._setup_database(raise_exception=False)
  49. def _setup_database(self, raise_exception=True):
  50. try:
  51. with db_connect(self.dbname, allow_uri=True).cursor() as cursor:
  52. cursor.autocommit(True)
  53. self._create_table(cursor)
  54. except:
  55. self._create_database()
  56. self._setup_database()
  57. def _create_database(self):
  58. with db_connect("postgres").cursor() as cursor:
  59. cursor.autocommit(True)
  60. cursor.execute("""
  61. CREATE DATABASE {dbname}
  62. ENCODING 'unicode'
  63. TEMPLATE 'template0';
  64. """.format(dbname=self.dbname))
  65. def _create_table(self, cursor):
  66. cursor.execute("""
  67. CREATE TABLE IF NOT EXISTS sessions (
  68. sid varchar PRIMARY KEY,
  69. write_date timestamp without time zone NOT NULL,
  70. payload text NOT NULL
  71. );
  72. """)
  73. @contextmanager
  74. def open_cursor(self):
  75. connection = db_connect(self.dbname, allow_uri=True)
  76. cursor = connection.cursor()
  77. cursor.autocommit(True)
  78. yield cursor
  79. cursor.close()
  80. @retry_database
  81. def save(self, session):
  82. with self.open_cursor() as cursor:
  83. cursor.execute("""
  84. INSERT INTO sessions (sid, write_date, payload)
  85. VALUES (%(sid)s, now() at time zone 'UTC', %(payload)s)
  86. ON CONFLICT (sid)
  87. DO UPDATE SET payload = %(payload)s, write_date = now() at time zone 'UTC';
  88. """, dict(sid=session.sid, payload=json.dumps(dict(session))))
  89. @retry_database
  90. def delete(self, session):
  91. with self.open_cursor() as cursor:
  92. cursor.execute("DELETE FROM sessions WHERE sid=%s;", [session.sid])
  93. @retry_database
  94. def get(self, sid):
  95. if not self.is_valid_key(sid):
  96. return self.new()
  97. with self.open_cursor() as cursor:
  98. cursor.execute("""
  99. SELECT payload, write_date
  100. FROM sessions WHERE sid=%s;
  101. """, [sid])
  102. try:
  103. payload, write_date = cursor.fetchone()
  104. if write_date.date() != datetime.today().date():
  105. cursor.execute("""
  106. UPDATE sessions
  107. SET write_date = now() at time zone 'UTC'
  108. WHERE sid=%s;
  109. """, [sid])
  110. return self.session_class(json.loads(payload), sid, False)
  111. except Exception:
  112. return self.session_class({}, sid, False)
  113. @retry_database
  114. def list(self):
  115. with self.open_cursor() as cursor:
  116. cursor.execute("SELECT sid FROM sessions;")
  117. return [record[0] for record in cursor.fetchall()]
  118. @retry_database
  119. def clean(self):
  120. with self.open_cursor() as cursor:
  121. cursor.execute("""
  122. DELETE FROM sessions
  123. WHERE now() at time zone 'UTC' - write_date > '7 days';
  124. """)