Server side session management support.
							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
 | 
				
			||||||
    ##########################################################################
 | 
					    ##########################################################################
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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