mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-01-24 07:16:52 -06:00
Server side session management support.
This commit is contained in:
parent
8189b39b1e
commit
3c366fafe7
@ -78,7 +78,10 @@ CONSOLE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
|
||||
FILE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
|
||||
|
||||
# Log file name
|
||||
LOG_FILE = 'pgadmin4.log'
|
||||
LOG_FILE = os.path.join(
|
||||
os.path.realpath(os.path.expanduser('~/.pgadmin/')),
|
||||
'pgadmin4.log'
|
||||
)
|
||||
|
||||
##########################################################################
|
||||
# Server settings
|
||||
@ -144,9 +147,34 @@ SETTINGS_SCHEMA_VERSION = 8
|
||||
# settings. This default places the file in the same directory as this
|
||||
# config file, but generates an absolute path for use througout the app.
|
||||
SQLITE_PATH = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'pgadmin4.db'
|
||||
)
|
||||
os.path.realpath(os.path.expanduser('~/.pgadmin/')),
|
||||
'pgadmin4.db'
|
||||
)
|
||||
|
||||
##########################################################################
|
||||
# Server-side session storage path
|
||||
#
|
||||
# SESSION_DB_PATH (Default: $HOME/.pgadmin4/sessions)
|
||||
##########################################################################
|
||||
#
|
||||
# We use SQLite for server-side session storage. There will be one
|
||||
# SQLite database object per session created.
|
||||
#
|
||||
# Specify the path used to store your session objects.
|
||||
#
|
||||
# If the specified directory does not exist, the setup script will create
|
||||
# it with permission mode 700 to keep the session database secure.
|
||||
#
|
||||
# On certain systems, you can use shared memory (tmpfs) for maximum
|
||||
# scalability, for example, on Ubuntu:
|
||||
#
|
||||
# SESSION_DB_PATH = '/run/shm/pgAdmin4_session'
|
||||
#
|
||||
##########################################################################
|
||||
SESSION_DB_PATH = os.path.join(
|
||||
os.path.realpath(os.path.expanduser('~/.pgadmin/')),
|
||||
'sessions'
|
||||
)
|
||||
|
||||
##########################################################################
|
||||
# Mail server settings
|
||||
|
@ -24,6 +24,8 @@ from werkzeug.utils import find_modules
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from pgadmin.utils.session import ServerSideSessionInterface
|
||||
|
||||
|
||||
# Configuration settings
|
||||
import config
|
||||
@ -99,6 +101,11 @@ def create_app(app_name=config.APP_NAME):
|
||||
app.jinja_env.trim_blocks = True
|
||||
app.config.from_object(config)
|
||||
|
||||
##########################################################################
|
||||
# Setup session management
|
||||
##########################################################################
|
||||
app.session_interface = ServerSideSessionInterface(config.SESSION_DB_PATH)
|
||||
|
||||
##########################################################################
|
||||
# Setup logging and log the application startup
|
||||
##########################################################################
|
||||
|
235
web/pgadmin/utils/session.py
Normal file
235
web/pgadmin/utils/session.py
Normal file
@ -0,0 +1,235 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""
|
||||
Implements the server-side session management.
|
||||
|
||||
Credit/Reference: http://flask.pocoo.org/snippets/86/
|
||||
|
||||
Modified to support both Python 2.6+ & Python 3.x
|
||||
"""
|
||||
|
||||
import os
|
||||
import errno
|
||||
import sqlite3
|
||||
from uuid import uuid4
|
||||
try:
|
||||
from cPickle import dumps, loads
|
||||
except:
|
||||
from pickle import dumps, loads
|
||||
|
||||
from collections import MutableMapping
|
||||
from flask import request
|
||||
from flask.sessions import SessionInterface, SessionMixin
|
||||
|
||||
|
||||
class SqliteSessionStorage(MutableMapping, SessionMixin):
|
||||
"""
|
||||
A class to store the session as sqlite object.
|
||||
"""
|
||||
|
||||
_create_sql = (
|
||||
'CREATE TABLE IF NOT EXISTS pg_session '
|
||||
'('
|
||||
' key TEXT PRIMARY KEY,'
|
||||
' val BLOB'
|
||||
')'
|
||||
)
|
||||
_get_sql = 'SELECT val FROM pg_session WHERE key = ?'
|
||||
_set_sql = 'REPLACE INTO pg_session (key, val) VALUES (?, ?)'
|
||||
_del_sql = 'DELETE FROM pg_session WHERE key = ?'
|
||||
_ite_sql = 'SELECT key FROM pg_session'
|
||||
_len_sql = 'SELECT COUNT(*) FROM pg_session'
|
||||
|
||||
def __init__(self, directory, sid, *args, **kwargs):
|
||||
"""Initialize the session storage for this particular session. If
|
||||
requires, creates new sqlite database per session (if require).
|
||||
"""
|
||||
self.path = os.path.join(directory, sid)
|
||||
self.directory = directory
|
||||
self.sid = sid
|
||||
self.modified = False
|
||||
self.conn = None
|
||||
if not os.path.exists(self.path):
|
||||
sess_db = os.open(self.path, os.O_CREAT, int("600", 8))
|
||||
os.close(sess_db)
|
||||
|
||||
with self._get_conn() as conn:
|
||||
conn.execute(self._create_sql)
|
||||
self.new = True
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Reads the session data for the particular key from the sqlite
|
||||
database.
|
||||
"""
|
||||
key = dumps(key, 0)
|
||||
rv = None
|
||||
with self._get_conn() as conn:
|
||||
for row in conn.execute(self._get_sql, (key,)):
|
||||
rv = loads(str(row[0]))
|
||||
break
|
||||
if rv is None:
|
||||
raise KeyError('Key not in this session')
|
||||
return rv
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Stores the session data for the given key.
|
||||
"""
|
||||
key = dumps(key, 0)
|
||||
value = dumps(value, 2)
|
||||
with self._get_conn() as conn:
|
||||
conn.execute(self._set_sql, (key, sqlite3.Binary(value)))
|
||||
self.modified = True
|
||||
|
||||
def __delitem__(self, key):
|
||||
"""Removes the session data representing the key from the session.
|
||||
"""
|
||||
key = dumps(key, 0)
|
||||
with self._get_conn() as conn:
|
||||
conn.execute(self._del_sql, (key,))
|
||||
self.modified = True
|
||||
|
||||
def __iter__(self):
|
||||
"""Returns the iterator of the key, value pair stored under this
|
||||
session.
|
||||
"""
|
||||
with self._get_conn() as conn:
|
||||
for row in conn.execute(self._ite_sql):
|
||||
yield loads(str(row[0]))
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the number of keys stored in this session.
|
||||
"""
|
||||
with self._get_conn() as conn:
|
||||
for row in conn.execute(self._len_sql):
|
||||
return row[0]
|
||||
|
||||
def _get_conn(self):
|
||||
"""Connection object to the sqlite database object.
|
||||
"""
|
||||
if not self.conn:
|
||||
self.conn = sqlite3.Connection(self.path)
|
||||
return self.conn
|
||||
|
||||
# These proxy classes are needed in order
|
||||
# for this session implementation to work properly.
|
||||
# That is because sometimes flask will chain method calls
|
||||
# with session'setdefault' calls.
|
||||
# Eg: session.setdefault('_flashes', []).append(1)
|
||||
# With these proxies, the changes made by chained
|
||||
# method calls will be persisted back to the sqlite
|
||||
# database.
|
||||
class CallableAttributeProxy(object):
|
||||
"""
|
||||
A proxy class to represent the callable attributes of a object.
|
||||
"""
|
||||
|
||||
def __init__(self, session, key, obj, attr):
|
||||
"""Initialize the proxy instance for the callable attribute.
|
||||
"""
|
||||
self.session = session
|
||||
self.key = key
|
||||
self.obj = obj
|
||||
self.attr = attr
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Returns the callable attributes for this session.
|
||||
"""
|
||||
rv = self.attr(*args, **kwargs)
|
||||
self.session[self.key] = self.obj
|
||||
return rv
|
||||
|
||||
class PersistedObjectProxy(object):
|
||||
"""
|
||||
A proxy class to represent the persistent object.
|
||||
"""
|
||||
|
||||
def __init__(self, session, key, obj):
|
||||
"""Initialize the persitent objects under the session.
|
||||
"""
|
||||
self.session = session
|
||||
self.key = key
|
||||
self.obj = obj
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Returns the attribute of the persistent object representing by
|
||||
the name for this object.
|
||||
"""
|
||||
attr = getattr(self.obj, name)
|
||||
if callable(attr):
|
||||
return SqliteSessionStorage.CallableAttributeProxy(
|
||||
self.session, self.key, self.obj, attr
|
||||
)
|
||||
return attr
|
||||
|
||||
def setdefault(self, key, value):
|
||||
"""Sets the default value for the particular key in the session.
|
||||
"""
|
||||
|
||||
if key not in self:
|
||||
self[key] = value
|
||||
self.modified = True
|
||||
|
||||
return SqliteSessionStorage.PersistedObjectProxy(
|
||||
self, key, self[key]
|
||||
)
|
||||
|
||||
|
||||
class ServerSideSessionInterface(SessionInterface):
|
||||
"""
|
||||
Implements the SessionInterface to support saving/opening session
|
||||
as sqlite object.
|
||||
"""
|
||||
|
||||
def __init__(self, directory):
|
||||
"""Initialize the session interface, which uses the sqlite as local
|
||||
storage, and works as server side session manager.
|
||||
|
||||
It takes directory as parameter, and creates the directory with 700
|
||||
permission (if not exists).
|
||||
"""
|
||||
directory = os.path.abspath(directory)
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory, int('700', 8))
|
||||
self.directory = directory
|
||||
|
||||
def open_session(self, app, request):
|
||||
"""
|
||||
Returns the SqliteSessionStorage object representing this session.
|
||||
"""
|
||||
sid = request.cookies.get(app.session_cookie_name)
|
||||
if not sid:
|
||||
sid = str(uuid4())
|
||||
return SqliteSessionStorage(self.directory, sid)
|
||||
|
||||
def save_session(self, app, session, response):
|
||||
"""
|
||||
Saves/Detroys the session object.
|
||||
"""
|
||||
sid = request.cookies.get(app.session_cookie_name)
|
||||
domain = self.get_cookie_domain(app)
|
||||
if not session:
|
||||
try:
|
||||
if session is None:
|
||||
session = SqliteSessionStorage(self.directory, sid)
|
||||
os.unlink(session.path)
|
||||
except OSError as ex:
|
||||
if ex.errno != errno.ENOENT:
|
||||
raise
|
||||
if session.modified:
|
||||
response.delete_cookie(
|
||||
app.session_cookie_name,
|
||||
domain=domain
|
||||
)
|
||||
return
|
||||
cookie_exp = self.get_expiration_time(app, session)
|
||||
response.set_cookie(
|
||||
app.session_cookie_name, session.sid,
|
||||
expires=cookie_exp, httponly=True, domain=domain
|
||||
)
|
@ -287,6 +287,12 @@ Exiting...""" % (version.value))
|
||||
))
|
||||
do_upgrade(app, user_datastore, security, version)
|
||||
else:
|
||||
directory = os.path.dirname(config.SQLITE_PATH)
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory, int('700', 8))
|
||||
db_file = os.open(config.SQLITE_PATH, os.O_CREAT, int('600', 8))
|
||||
os.close(db_file)
|
||||
|
||||
print("""
|
||||
The configuration database - '{0}' does not exist.
|
||||
Entering initial setup mode...""".format(config.SQLITE_PATH))
|
||||
|
Loading…
Reference in New Issue
Block a user