diff --git a/web/migrations/versions/ef590e979b0d_.py b/web/migrations/versions/ef590e979b0d_.py new file mode 100644 index 0000000..be219c5 --- /dev/null +++ b/web/migrations/versions/ef590e979b0d_.py @@ -0,0 +1,46 @@ + +"""empty message + +Revision ID: ef590e979b0d +Revises: d85a62333272 +Create Date: 2017-08-23 18:37:14.836988 + +""" +from alembic import op +import sqlalchemy as sa +from pgadmin.model import db + +# revision identifiers, used by Alembic. +revision = 'ef590e979b0d' +down_revision = 'd85a62333272' +branch_labels = None +depends_on = None + + +def upgrade(): + db.engine.execute( + 'ALTER TABLE server ADD COLUMN passfile TEXT' + ) + db.engine.execute( + 'ALTER TABLE server ADD COLUMN sslcert TEXT' + ) + db.engine.execute( + 'ALTER TABLE server ADD COLUMN sslkey TEXT' + ) + db.engine.execute( + 'ALTER TABLE server ADD COLUMN sslrootcert TEXT' + ) + db.engine.execute( + 'ALTER TABLE server ADD COLUMN sslcrl TEXT' + ) + db.engine.execute( + 'ALTER TABLE server ADD COLUMN sslcompression ' + 'INTEGER default 0' + ) + db.engine.execute( + 'UPDATE server SET sslcompression=0' + ) + + +def downgrade(): + pass diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index a74436e..d21a8f6 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -230,7 +230,29 @@ class ServerNode(PGChildNodeView): '((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$' pat4 = re.compile(EXP_IP4) pat6 = re.compile(EXP_IP6) + SSL_MODES = ['require', 'verify-ca', 'verify-full'] + def check_ssl_fields(self, data): + """ + This function will allow us to check and set defaults for + SSL fields + + Args: + data: Response data + + Returns: + Flag and Data + """ + flag = False + if 'sslmode' in data and data['sslmode'] in self.SSL_MODES: + flag = True + ssl_fields = [ + 'sslcert', 'sslkey', 'sslrootcert', 'sslcrl', 'sslcompression' + ] + for field in ssl_fields: + if field not in data: + data[field] = None + return flag, data def nodes(self, gid): res = [] @@ -381,7 +403,13 @@ class ServerNode(PGChildNodeView): 'gid': 'servergroup_id', 'comment': 'comment', 'role': 'role', - 'db_res': 'db_res' + 'db_res': 'db_res', + 'passfile': 'passfile', + 'sslcert': 'sslcert', + 'sslkey': 'sslkey', + 'sslrootcert': 'sslrootcert', + 'sslcrl': 'sslcrl', + 'sslcompression': 'sslcompression' } disp_lbl = { @@ -411,8 +439,6 @@ class ServerNode(PGChildNodeView): errormsg=gettext('Host address not valid') ) - - manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) conn = manager.connection() connected = conn.connected() @@ -430,7 +456,12 @@ class ServerNode(PGChildNodeView): for arg in config_param_map: if arg in data: - setattr(server, config_param_map[arg], data[arg]) + value = data[arg] + # sqlite3 do not have boolean type so we need to convert + # it manually to integer + if arg == 'sslcompression': + value = 1 if value else 0 + setattr(server, config_param_map[arg], value) idx += 1 if idx == 0: @@ -532,6 +563,8 @@ class ServerNode(PGChildNodeView): conn = manager.connection() connected = conn.connected() + is_ssl = True if server.ssl_mode in self.SSL_MODES else False + return ajax_response( response={ 'id': server.id, @@ -549,7 +582,14 @@ class ServerNode(PGChildNodeView): 'version': manager.ver, 'sslmode': server.ssl_mode, 'server_type': manager.server_type if connected else 'pg', - 'db_res': server.db_res.split(',') if server.db_res else None + 'db_res': server.db_res.split(',') if server.db_res else None, + 'passfile': server.passfile if server.passfile else None, + 'sslcert': server.sslcert if is_ssl else None, + 'sslkey': server.sslkey if is_ssl else None, + 'sslrootcert': server.sslrootcert if is_ssl else None, + 'sslcrl': server.sslcrl if is_ssl else None, + 'sslcompression': True if is_ssl and server.sslcompression + else False } ) @@ -588,6 +628,9 @@ class ServerNode(PGChildNodeView): errormsg=gettext('Host address not valid') ) + # To check ssl configuration + is_ssl, data = self.check_ssl_fields(data) + server = None try: @@ -603,7 +646,12 @@ class ServerNode(PGChildNodeView): ssl_mode=data[u'sslmode'], comment=data[u'comment'] if u'comment' in data else None, role=data[u'role'] if u'role' in data else None, - db_res=','.join(data[u'db_res']) if u'db_res' in data else None + db_res=','.join(data[u'db_res']) if u'db_res' in data else None, + sslcert=data['sslcert'] if is_ssl else None, + sslkey=data['sslkey'] if is_ssl else None, + sslrootcert=data['sslrootcert'] if is_ssl else None, + sslcrl=data['sslcrl'] if is_ssl else None, + sslcompression=1 if is_ssl and data['sslcompression'] else 0 ) db.session.add(server) db.session.commit() @@ -621,14 +669,21 @@ class ServerNode(PGChildNodeView): if 'password' in data and data["password"] != '': # login with password have_password = True + passfile = None password = data['password'] password = encrypt(password, current_user.password) + elif 'passfile' in data and data["passfile"] != '': + passfile = data['passfile'] + setattr(server, 'passfile', passfile) + db.session.commit() else: # Attempt password less login password = None + passfile = None status, errmsg = conn.connect( password=password, + passfile=passfile, server_types=ServerType.types() ) if hasattr(str, 'decode') and errmsg is not None: @@ -774,6 +829,7 @@ class ServerNode(PGChildNodeView): ) if request.data else {} password = None + passfile = None save_password = False # Connect the Server @@ -782,7 +838,8 @@ class ServerNode(PGChildNodeView): if 'password' not in data: conn_passwd = getattr(conn, 'password', None) - if conn_passwd is None and server.password is None: + if conn_passwd is None and server.password is None and \ + server.passfile is None: # Return the password template in case password is not # provided, or password has not been saved earlier. return make_json_response( @@ -795,6 +852,8 @@ class ServerNode(PGChildNodeView): _=gettext ) ) + elif server.passfile and server.passfile != '': + passfile = server.passfile else: password = conn_passwd or server.password else: @@ -815,6 +874,7 @@ class ServerNode(PGChildNodeView): try: status, errmsg = conn.connect( password=password, + passfile=passfile, server_types=ServerType.types() ) except Exception as e: diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js index ea25ab8..c3dd4df 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js @@ -611,7 +611,13 @@ define('pgadmin.node.server', [ connect_now: true, password: undefined, save_password: false, - db_res: '' + db_res: '', + passfile: undefined, + sslcompression: false, + sslcert: undefined, + sslkey: undefined, + sslrootcert: undefined, + sslcrl: undefined }, // Default values! initialize: function(attrs, args) { @@ -698,6 +704,48 @@ define('pgadmin.node.server', [ {label: 'Verify-CA', value: 'verify-ca'}, {label: 'Verify-Full', value: 'verify-full'} ] + },{ + id: 'passfile', label: gettext('Password File'), type: 'text', + group: gettext('Advanced'), mode: ['edit', 'create'], + disabled: 'isConnected', control: Backform.FileControl, + dialog_type: 'select_file', supp_types: ['*'] + },{ + id: 'sslcert', label: gettext('Client certificate'), type: 'text', + group: gettext('SSL Configuration'), mode: ['edit', 'create'], + disabled: 'isSSL', control: Backform.FileControl, + dialog_type: 'select_file', supp_types: ['*'], + deps: ['sslmode'] + },{ + id: 'sslkey', label: gettext('Client certificate key'), type: 'text', + group: gettext('SSL Configuration'), mode: ['edit', 'create'], + disabled: 'isSSL', control: Backform.FileControl, + dialog_type: 'select_file', supp_types: ['*'], + deps: ['sslmode'] + },{ + id: 'sslrootcert', label: gettext('Root certificate'), type: 'text', + group: gettext('SSL Configuration'), mode: ['edit', 'create'], + disabled: 'isSSL', control: Backform.FileControl, + dialog_type: 'select_file', supp_types: ['*'], + deps: ['sslmode'] + },{ + id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text', + group: gettext('SSL Configuration'), mode: ['edit', 'create'], + disabled: 'isSSL', control: Backform.FileControl, + dialog_type: 'select_file', supp_types: ['*'], + deps: ['sslmode'] + },{ + id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch', + mode: ['edit', 'create'], group: gettext('SSL Configuration'), + 'options': { 'onText': 'True', 'offText': 'False', + 'onColor': 'success', 'offColor': 'danger', 'size': 'small'}, + deps: ['sslmode'], disabled: 'isSSL' + },{ + id: 'passfile', label: gettext('Password File'), type: 'text', + group: gettext('Advanced'), mode: ['properties'], + visible: function(m) { + var passfile = m.get('passfile'); + return !_.isUndefined(passfile) && !_.isNull(passfile); + } }], validate: function() { var err = {}, @@ -790,6 +838,14 @@ define('pgadmin.node.server', [ }, isConnected: function(model) { return model.get('connected'); + }, + isSSL: function(model) { + var ssl_mode = model.get('sslmode'); + // If server is not connected and have required SSL option + if(model.get('connected')) { + return true; + } + return _.indexOf(['require', 'verify-ca', 'verify-full'], ssl_mode) == -1; } }), connection_lost: function(i, resp) { diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index e1e1d4a..eb9dab3 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -129,8 +129,15 @@ class Server(db.Model): backref=db.backref('server', cascade="all, delete-orphan"), lazy='joined') db_res = db.Column(db.Text(), nullable=True) - - + passfile = db.Column(db.Text(), nullable=True) + sslcert = db.Column(db.Text(), nullable=True) + sslkey = db.Column(db.Text(), nullable=True) + sslrootcert = db.Column(db.Text(), nullable=True) + sslcrl = db.Column(db.Text(), nullable=True) + sslcompression =db.Column(db.Integer(), + db.CheckConstraint( + 'sslcompression >= 0 AND sslcompression <= 1' + ), nullable=False) class ModulePreference(db.Model): @@ -220,4 +227,4 @@ 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 + value = db.Column(db.String(), nullable=False) diff --git a/web/pgadmin/utils/driver/psycopg2/__init__.py b/web/pgadmin/utils/driver/psycopg2/__init__.py index 7cda5ef..a2826a1 100644 --- a/web/pgadmin/utils/driver/psycopg2/__init__.py +++ b/web/pgadmin/utils/driver/psycopg2/__init__.py @@ -295,9 +295,11 @@ class Connection(BaseConnection): pg_conn = None password = None + passfile = None mgr = self.manager encpass = kwargs['password'] if 'password' in kwargs else None + passfile = kwargs['passfile'] if 'passfile' in kwargs else None if encpass is None: encpass = self.password or getattr(mgr, 'password', None) @@ -350,7 +352,13 @@ class Connection(BaseConnection): user=user, password=password, async=self.async, - sslmode=mgr.ssl_mode + passfile=passfile, + sslmode=mgr.ssl_mode, + sslcert=mgr.sslcert, + sslkey=mgr.sslkey, + sslrootcert=mgr.sslrootcert, + sslcrl=mgr.sslcrl, + sslcompression=True if mgr.sslcompression else False ) # If connection is asynchronous then we will have to wait @@ -1192,7 +1200,14 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id} port=mgr.port, database=self.db, user=mgr.user, - password=password + password=password, + passfile=mgr.passfile, + sslmode=mgr.ssl_mode, + sslcert=mgr.sslcert, + sslkey=mgr.sslkey, + sslrootcert=mgr.sslrootcert, + sslcrl=mgr.sslcrl, + sslcompression=True if mgr.sslcompression else False ) except psycopg2.Error as e: @@ -1461,7 +1476,15 @@ Failed to reset the connection to the server due to following error: port=self.manager.port, database=self.db, user=self.manager.user, - password=password + password=password, + passfile=self.manager.passfile, + sslmode=self.manager.ssl_mode, + sslcert=self.manager.sslcert, + sslkey=self.manager.sslkey, + sslrootcert=self.manager.sslrootcert, + sslcrl=self.manager.sslcrl, + sslcompression=True if self.manager.sslcompression + else False ) # Get the cursor and run the query @@ -1616,6 +1639,12 @@ class ServerManager(object): self.db_info = dict() self.server_types = None self.db_res = server.db_res + self.passfile = server.passfile + self.sslcert = server.sslcert + self.sslkey = server.sslkey + self.sslrootcert = server.sslrootcert + self.sslcrl = server.sslcrl + self.sslcompression = True if server.sslcompression else False for con in self.connections: self.connections[con]._release()