mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -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'
|
FILE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
|
||||||
|
|
||||||
# Log file name
|
# Log file name
|
||||||
LOG_FILE = 'pgadmin4.log'
|
LOG_FILE = os.path.join(
|
||||||
|
os.path.realpath(os.path.expanduser('~/.pgadmin/')),
|
||||||
|
'pgadmin4.log'
|
||||||
|
)
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# Server settings
|
# Server settings
|
||||||
@ -144,9 +147,34 @@ SETTINGS_SCHEMA_VERSION = 8
|
|||||||
# settings. This default places the file in the same directory as this
|
# settings. This default places the file in the same directory as this
|
||||||
# config file, but generates an absolute path for use througout the app.
|
# config file, but generates an absolute path for use througout the app.
|
||||||
SQLITE_PATH = os.path.join(
|
SQLITE_PATH = os.path.join(
|
||||||
os.path.dirname(os.path.realpath(__file__)),
|
os.path.realpath(os.path.expanduser('~/.pgadmin/')),
|
||||||
'pgadmin4.db'
|
'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
|
# Mail server settings
|
||||||
|
@ -24,6 +24,8 @@ from werkzeug.utils import find_modules
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from pgadmin.utils.session import ServerSideSessionInterface
|
||||||
|
|
||||||
|
|
||||||
# Configuration settings
|
# Configuration settings
|
||||||
import config
|
import config
|
||||||
@ -99,6 +101,11 @@ def create_app(app_name=config.APP_NAME):
|
|||||||
app.jinja_env.trim_blocks = True
|
app.jinja_env.trim_blocks = True
|
||||||
app.config.from_object(config)
|
app.config.from_object(config)
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# Setup session management
|
||||||
|
##########################################################################
|
||||||
|
app.session_interface = ServerSideSessionInterface(config.SESSION_DB_PATH)
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# Setup logging and log the application startup
|
# 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)
|
do_upgrade(app, user_datastore, security, version)
|
||||||
else:
|
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("""
|
print("""
|
||||||
The configuration database - '{0}' does not exist.
|
The configuration database - '{0}' does not exist.
|
||||||
Entering initial setup mode...""".format(config.SQLITE_PATH))
|
Entering initial setup mode...""".format(config.SQLITE_PATH))
|
||||||
|
Loading…
Reference in New Issue
Block a user