diff --git a/web/config.py b/web/config.py index 20714f9fd..8411d79d6 100644 --- a/web/config.py +++ b/web/config.py @@ -140,21 +140,13 @@ DEFAULT_SERVER_PORT = 5050 # Enable CSRF protection? CSRF_ENABLED = True -# Secret key for signing CSRF data. Override this in config_local.py if -# running on a web server -CSRF_SESSION_KEY = 'SuperSecret1' - -# Secret key for signing cookies. Override this in config_local.py if -# running on a web server -SECRET_KEY = 'SuperSecret2' - -# Salt used when hashing passwords. Override this in config_local.py if -# running on a web server -SECURITY_PASSWORD_SALT = 'SuperSecret3' - # Hashing algorithm used for password storage SECURITY_PASSWORD_HASH = 'pbkdf2_sha512' +# NOTE: CSRF_SESSION_KEY, SECRET_KEY and SECURITY_PASSWORD_SALT are no +# longer part of the main configuration, but are stored in the +# configuration databases 'keys' table and are auto-generated. + # Should HTML be minified on the fly when not in debug mode? # Note: This is disabled by default as it will error when processing the # docs. If the serving of docs is handled by an Apache HTTPD diff --git a/web/pgAdmin4.py b/web/pgAdmin4.py index 1fb34f932..f894f8b3b 100644 --- a/web/pgAdmin4.py +++ b/web/pgAdmin4.py @@ -32,18 +32,6 @@ config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION # Sanity checks ########################################################################## -# Check for local settings if running in server mode -if config.SERVER_MODE is True: - local_config = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'config_local.py') - if not os.path.isfile(local_config): - print("The configuration file %s does not exist.\n" % local_config) - print("Before running this application, ensure that config_local.py has been created") - print("and sets values for SECRET_KEY, SECURITY_PASSWORD_SALT and CSRF_SESSION_KEY") - print("at bare minimum. See config.py for more information and a complete list of") - print("settings. Exiting...") - sys.exit(1) - # Check if the database exists. If it does not, create it. if not os.path.isfile(config.SQLITE_PATH): setupfile = os.path.join(os.path.dirname(os.path.realpath(__file__)), diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py index d988172ef..79fa1c60d 100644 --- a/web/pgadmin/__init__.py +++ b/web/pgadmin/__init__.py @@ -26,7 +26,7 @@ from pgadmin.utils.session import create_session_interface from werkzeug.local import LocalProxy from werkzeug.utils import find_modules -from pgadmin.model import db, Role, Server, ServerGroup, User, Version +from pgadmin.model import db, Role, Server, ServerGroup, User, Version, Keys # Configuration settings import config @@ -126,11 +126,6 @@ def create_app(app_name=config.APP_NAME): app.config.from_object(config) app.config.update(dict(PROPAGATE_EXCEPTIONS=True)) - ########################################################################## - # Setup session management - ########################################################################## - app.session_interface = create_session_interface(app) - ########################################################################## # Setup logging and log the application startup ########################################################################## @@ -206,7 +201,7 @@ def create_app(app_name=config.APP_NAME): # Setup Flask-Security user_datastore = SQLAlchemyUserDatastore(db, User, Role) - security = Security(app, user_datastore) + security = Security(None, user_datastore) # Upgrade the schema (if required) with app.app_context(): @@ -220,9 +215,29 @@ def create_app(app_name=config.APP_NAME): ) ) from setup import do_upgrade - do_upgrade(app, user_datastore, security, version) + do_upgrade(app, user_datastore, version) + ########################################################################## + # Setup security + ########################################################################## + with app.app_context(): + config.CSRF_SESSION_KEY = Keys.query.filter_by(name = 'CSRF_SESSION_KEY').first().value + config.SECRET_KEY = Keys.query.filter_by(name = 'SECRET_KEY').first().value + config.SECURITY_PASSWORD_SALT = Keys.query.filter_by(name = 'SECURITY_PASSWORD_SALT').first().value + + # Update the app.config with proper security keyes for signing CSRF data, + # signing cookies, and the SALT for hashing the passwords. + app.config.update(dict(CSRF_SESSION_KEY=config.CSRF_SESSION_KEY)) + app.config.update(dict(SECRET_KEY=config.SECRET_KEY)) + app.config.update(dict(SECURITY_PASSWORD_SALT=config.SECURITY_PASSWORD_SALT)) + + security.init_app(app) + + app.session_interface = create_session_interface(app) + + ########################################################################## # Load all available server drivers + ########################################################################## driver.init_app(app) ########################################################################## diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 019e9b147..9727d2b05 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy # ########################################################################## -SCHEMA_VERSION = 13 +SCHEMA_VERSION = 14 ########################################################################## # @@ -207,3 +207,10 @@ class Process(db.Model): end_time = db.Column(db.String(), nullable=True) exit_code = db.Column(db.Integer(), nullable=True) acknowledge = db.Column(db.String(), nullable=True) + + +class Keys(db.Model): + """Define the keys table.""" + __tablename__ = 'keys' + name = db.Column(db.String(), nullable=False, primary_key=True) + value = db.Column(db.String(), nullable=False) \ No newline at end of file diff --git a/web/setup.py b/web/setup.py index 64273fb18..12465f366 100755 --- a/web/setup.py +++ b/web/setup.py @@ -10,6 +10,7 @@ """Perform the initial setup of the application, by creating the auth and settings database.""" +import base64 import getpass import os import random @@ -22,7 +23,7 @@ from flask_security import Security, SQLAlchemyUserDatastore from flask_security.utils import encrypt_password from pgadmin.model import db, Role, User, Server, \ - ServerGroup, Version + ServerGroup, Version, Keys # Configuration settings import config @@ -40,6 +41,7 @@ if hasattr(__builtins__, 'raw_input'): def do_setup(app): """Create a new settings database from scratch""" + if config.SERVER_MODE is False: print("NOTE: Configuring authentication for DESKTOP mode.") email = config.DESKTOP_USER @@ -116,6 +118,17 @@ def do_setup(app): name='ConfigDB', value=config.SETTINGS_SCHEMA_VERSION ) db.session.merge(version) + db.session.commit() + + # Create the keys + key = Keys(name='CSRF_SESSION_KEY', value=config.CSRF_SESSION_KEY) + db.session.merge(key) + + key = Keys(name='SECRET_KEY', value=config.SECRET_KEY) + db.session.merge(key) + + key = Keys(name='SECURITY_PASSWORD_SALT', value=config.SECURITY_PASSWORD_SALT) + db.session.merge(key) db.session.commit() @@ -128,7 +141,7 @@ def do_setup(app): ) -def do_upgrade(app, datastore, security, version): +def do_upgrade(app, datastore, version): """Upgrade an existing settings database""" ####################################################################### # Run whatever is required to update the database schema to the current @@ -329,6 +342,29 @@ ALTER TABLE SERVER ADD COLUMN discovery_id TEXT """) + if int(version.value) < 14: + db.engine.execute(""" +CREATE TABLE keys ( + name TEST NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (name)) + """) + + sql = "INSERT INTO keys (name, value) VALUES ('CSRF_SESSION_KEY', '%s')" % base64.urlsafe_b64encode(os.urandom(32)) + db.engine.execute(sql) + + sql = "INSERT INTO keys (name, value) VALUES ('SECRET_KEY', '%s')" % base64.urlsafe_b64encode(os.urandom(32)) + db.engine.execute(sql) + + # If SECURITY_PASSWORD_SALT is not in the config, but we're upgrading, then it must (unless the + # user edited the main config - which they shouldn't have done) have been at it's default + # value, so we'll use that. Otherwise, use whatever we can find in the config. + if hasattr(config, 'SECURITY_PASSWORD_SALT'): + sql = "INSERT INTO keys (name, value) VALUES ('SECURITY_PASSWORD_SALT', '%s')" % config.SECURITY_PASSWORD_SALT + else: + sql = "INSERT INTO keys (name, value) VALUES ('SECURITY_PASSWORD_SALT', 'SuperSecret3')" + db.engine.execute(sql) + # Finally, update the schema version version.value = config.SETTINGS_SCHEMA_VERSION db.session.merge(version) @@ -347,6 +383,7 @@ ALTER TABLE SERVER ############################################################################### if __name__ == '__main__': app = Flask(__name__) + app.config.from_object(config) if config.TESTING_MODE: @@ -364,15 +401,6 @@ if __name__ == '__main__': 'config_local.py' ) - if not os.path.isfile(local_config): - print(""" - The configuration file - {0} does not exist. - Before running this application, ensure that config_local.py has been created - and sets values for SECRET_KEY, SECURITY_PASSWORD_SALT and CSRF_SESSION_KEY - at bare minimum. See config.py for more information and a complete list of - settings. Exiting...""".format(local_config)) - sys.exit(1) - # Check if the database exists. If it does, tell the user and exit. if os.path.isfile(config.SQLITE_PATH): print(""" @@ -381,7 +409,6 @@ Entering upgrade mode...""" % config.SQLITE_PATH) # Setup Flask-Security user_datastore = SQLAlchemyUserDatastore(db, User, Role) - security = Security(app, user_datastore) # Always use "< REQUIRED_VERSION" as the test for readability with app.app_context(): @@ -403,8 +430,13 @@ Exiting...""" % (version.value)) print("NOTE: Upgrading database schema from version %d to %d." % ( version.value, config.SETTINGS_SCHEMA_VERSION )) - do_upgrade(app, user_datastore, security, version) + do_upgrade(app, user_datastore, version) else: + # Get some defaults for the various keys + config.CSRF_SESSION_KEY = base64.urlsafe_b64encode(os.urandom(32)) + config.SECRET_KEY = base64.urlsafe_b64encode(os.urandom(32)) + config.SECURITY_PASSWORD_SALT = base64.urlsafe_b64encode(os.urandom(32)) + directory = os.path.dirname(config.SQLITE_PATH) if not os.path.exists(directory): os.makedirs(directory, int('700', 8))