diff --git a/web/config.py b/web/config.py index 8398da156..a42abd75e 100644 --- a/web/config.py +++ b/web/config.py @@ -123,6 +123,10 @@ MINIFY_HTML = True; # User account and settings storage ########################################################################## +# The schema version number for the configuration database +# DO NOT CHANGE UNLESS YOU ARE A PGADMIN DEVELOPER!! +SETTINGS_SCHEMA_VERSION = 2 + # The default path to the SQLite database used to store user accounts and # settings. This default places the file in the same directory as this # config file, but generates an absolute path for use througout the app. diff --git a/web/pgadmin/settings/settings_model.py b/web/pgadmin/settings/settings_model.py index 9d08345cb..731137b69 100644 --- a/web/pgadmin/settings/settings_model.py +++ b/web/pgadmin/settings/settings_model.py @@ -7,7 +7,16 @@ # ########################################################################## -"""Defines the models for the configuration database.""" +"""Defines the models for the configuration database. + +If any of the models are updated, you (yes, you, the developer) MUST do two +things: + +1) Increment SETTINGS_SCHEMA_VERSION in config.py + +2) Modify setup.py to ensure that the appropriate changes are made to the config + database to upgrade it to the new version. +""" from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.security import UserMixin, RoleMixin @@ -19,21 +28,26 @@ roles_users = db.Table('roles_users', db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) +class Version(db.Model): + """Version numbers for reference/upgrade purposes""" + __tablename__ = 'version' + name = db.Column(db.String(32), primary_key=True) + value = db.Column(db.Integer(), nullable=False) + class Role(db.Model, RoleMixin): """Define a security role""" __tablename__ = 'role' id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(80), unique=True) - description = db.Column(db.String(255)) + name = db.Column(db.String(128), unique=True, nullable=False) + description = db.Column(db.String(256), nullable=False) class User(db.Model, UserMixin): """Define a user object""" __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) - email = db.Column(db.String(255), unique=True) - password = db.Column(db.String(255)) - active = db.Column(db.Boolean()) - active = db.Column(db.Boolean()) + email = db.Column(db.String(256), unique=True, nullable=False) + password = db.Column(db.String(256)) + active = db.Column(db.Boolean(), nullable=False) confirmed_at = db.Column(db.DateTime()) roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) @@ -42,14 +56,29 @@ class Setting(db.Model): """Define a setting object""" __tablename__ = 'setting' user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) - setting = db.Column(db.String(255), primary_key=True) + setting = db.Column(db.String(256), primary_key=True) value = db.Column(db.String(1024)) - + class ServerGroup(db.Model): """Define a server group for the treeview""" __tablename__ = 'servergroup' id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id')) - name = db.Column(db.String(80)) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + name = db.Column(db.String(128), nullable=False) __table_args__ = (db.UniqueConstraint('user_id', 'name'),) + +class Server(db.Model): + """Define a registered Postgres server""" + __tablename__ = 'server' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + servergroup_id = db.Column(db.Integer, db.ForeignKey('servergroup.id'), nullable=False) + name = db.Column(db.String(128), nullable=False) + host = db.Column(db.String(128), nullable=False) + port = db.Column(db.Integer(), db.CheckConstraint('port >= 1024 AND port <= 65534'), nullable=False) + maintenance_db = db.Column(db.String(64), nullable=False) + username = db.Column(db.String(64), nullable=False) + ssl_mode = db.Column(db.String(16), nullable=False) + + diff --git a/web/setup.py b/web/setup.py index 7cf01b2bd..c7398f53d 100644 --- a/web/setup.py +++ b/web/setup.py @@ -14,7 +14,7 @@ from flask import Flask from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.security import Security, SQLAlchemyUserDatastore from flask.ext.security.utils import encrypt_password -from pgadmin.settings.settings_model import db, Role, User, ServerGroup +from pgadmin.settings.settings_model import db, Role, User, Server, ServerGroup, Version import getpass, os, random, sys, string @@ -26,6 +26,100 @@ app.config.from_object(config) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + config.SQLITE_PATH.replace('\\', '/') db.init_app(app) +def do_setup(): + """Create a new settings database from scratch""" + if config.SERVER_MODE == False: + print "NOTE: Configuring authentication for DESKTOP mode." + email = config.DESKTOP_USER + p1 = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(32)]) + + else: + print "NOTE: Configuring authentication for SERVER mode.\n" + + # Prompt the user for their default username and password. + print "Enter the email address and password to use for the initial pgAdmin user account:\n" + email = '' + while email == '': + email = raw_input("Email address: ") + + pprompt = lambda: (getpass.getpass(), getpass.getpass('Retype password: ')) + + p1, p2 = pprompt() + while p1 != p2: + print('Passwords do not match. Try again') + p1, p2 = pprompt() + + # Setup Flask-Security + user_datastore = SQLAlchemyUserDatastore(db, User, Role) + security = Security(app, user_datastore) + + with app.app_context(): + password = encrypt_password(p1) + + db.create_all() + user_datastore.create_role(name='Administrators', description='pgAdmin Administrators Role') + user_datastore.create_user(email=email, password=password) + user_datastore.add_role_to_user(email, 'Administrators') + + # Get the user's ID and create the default server group + user = User.query.filter_by(email=email).first() + server_group = ServerGroup(user_id=user.id, name="Servers") + db.session.merge(server_group) + + # Set the schema version + version = Version(name='ConfigDB', value=config.SETTINGS_SCHEMA_VERSION) + db.session.merge(version) + + db.session.commit() + + # Done! + print "" + print "The configuration database has been created at %s" % config.SQLITE_PATH + +def do_upgrade(): + """Upgrade an existing settings database""" + # Setup Flask-Security + user_datastore = SQLAlchemyUserDatastore(db, User, Role) + security = Security(app, user_datastore) + + with app.app_context(): + version = Version.query.filter_by(name='ConfigDB').first() + + # Pre-flight checks + if int(version.value) > int(config.SETTINGS_SCHEMA_VERSION): + print "The database schema version is %d, whilst the version required by the software is %d.\nExiting..." \ + % (version.value, config.SETTINGS_SCHEMA_VERSION) + sys.exit(1) + elif int(version.value) == int(config.SETTINGS_SCHEMA_VERSION): + print "The database schema version is %d as required.\nExiting..." % (version.value) + sys.exit(1) + + print "NOTE: Upgrading database schema from version %d to %d." % (version.value, config.SETTINGS_SCHEMA_VERSION) + + ####################################################################### + # Run whatever is required to update the database schema to the current + # version. Always use "< REQUIRED_VERSION" as the test for readability + ####################################################################### + + # Changes introduced in schema version 2 + if int(version.value) < 2: + # Create the 'server' table + db.metadata.create_all(db.engine, tables=[Server.__table__]) + + # Finally, update the schema version + version.value = config.SETTINGS_SCHEMA_VERSION + db.session.merge(version) + + db.session.commit() + + # Done! + print "" + print "The configuration database %s has been upgraded to version %d" % (config.SQLITE_PATH, config.SETTINGS_SCHEMA_VERSION) + +############################################################################### +# Do stuff! +############################################################################### + print "pgAdmin 4 - Application Initialisation" print "======================================\n" @@ -40,49 +134,8 @@ if not os.path.isfile(local_config): # Check if the database exists. If it does, tell the user and exit. if os.path.isfile(config.SQLITE_PATH): - print "The configuration database %s already exists and will not be overwritten.\nExiting..." % config.SQLITE_PATH - sys.exit(1) - -if config.SERVER_MODE == False: - print "NOTE: Configuring authentication for DESKTOP mode." - email = config.DESKTOP_USER - p1 = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(32)]) - -else: - print "NOTE: Configuring authentication for SERVER mode.\n" - - # Prompt the user for their default username and password. - print "Enter the email address and password to use for the initial pgAdmin user account:\n" - email = '' - while email == '': - email = raw_input("Email address: ") - - pprompt = lambda: (getpass.getpass(), getpass.getpass('Retype password: ')) - - p1, p2 = pprompt() - while p1 != p2: - print('Passwords do not match. Try again') - p1, p2 = pprompt() - -# Setup Flask-Security -user_datastore = SQLAlchemyUserDatastore(db, User, Role) -security = Security(app, user_datastore) - -with app.app_context(): - password = encrypt_password(p1) - - db.create_all() - user_datastore.create_role(name='Administrators', description='pgAdmin Administrators Role') - user_datastore.create_user(email=email, password=password) - user_datastore.add_role_to_user(email, 'Administrators') - - # Get the user's ID and create the default server group - user = User.query.filter_by(email=email).first() - server_group = ServerGroup(user_id=user.id, name="Servers") - db.session.merge(server_group) - - db.session.commit() - -# Done! -print "" -print "The configuration database has been created at %s" % config.SQLITE_PATH + print "The configuration database %s already exists.\nEntering upgrade mode...\n" % config.SQLITE_PATH + do_upgrade() +else: + print "The configuration database %s does not exist.\nEntering initial setup mode...\n" % config.SQLITE_PATH + do_setup() \ No newline at end of file