diff --git a/requirements.txt b/requirements.txt index cc00a8d..7fcf677 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,3 +41,4 @@ sqlparse==0.1.19 Werkzeug==0.9.6 WTForms==2.0.2 backports.csv==1.0.4; python_version <= '2.7' +ipaddress==1.0.18 diff --git a/web/migrations/versions/3c1e4b6eda55_.py b/web/migrations/versions/3c1e4b6eda55_.py new file mode 100644 index 0000000..308e9eb --- /dev/null +++ b/web/migrations/versions/3c1e4b6eda55_.py @@ -0,0 +1,35 @@ + +"""empty message + +Revision ID: 3c1e4b6eda55 +Revises: 09d53fca90c7 +Create Date: 2017-06-13 17:05:30.671859 + +""" +import base64 + +import sys +from alembic import op +from pgadmin.model import db, Server +import config +import os +from pgadmin.setup import get_version + + +# revision identifiers, used by Alembic. +revision = '3c1e4b6eda55' +down_revision = '09d53fca90c7' +branch_labels = None +depends_on = None + + +def upgrade(): + verison = get_version() + + db.engine.execute( + 'ALTER TABLE server ADD COLUMN hostaddr TEXT(1024)' + ) + + +def downgrade(): + pass diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 3b37a1c..9392eee 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -25,7 +25,7 @@ import config from config import PG_DEFAULT_DRIVER from pgadmin.model import db, Server, ServerGroup, User from pgadmin.utils.driver import get_driver - +import ipaddress def has_any(data, keys): """ @@ -354,6 +354,7 @@ class ServerNode(PGChildNodeView): config_param_map = { 'name': 'name', 'host': 'host', + 'hostaddr': 'hostaddr', 'port': 'port', 'db': 'maintenance_db', 'username': 'username', @@ -379,13 +380,25 @@ class ServerNode(PGChildNodeView): request.data, encoding='utf-8' ) + if 'hostaddr' in data and data['hostaddr'] != '': + try: + ipaddress.ip_address(data['hostaddr']) + except ValueError as e: + return make_json_response( + success=0, + status=400, + errormsg=gettext('Host address not valid') + ) + + + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) conn = manager.connection() connected = conn.connected() if connected: for arg in ( - 'host', 'port', 'db', 'username', 'sslmode', 'role' + 'host', 'hostaddr', 'port', 'db', 'username', 'sslmode', 'role' ): if arg in data: return forbidden( @@ -399,6 +412,7 @@ class ServerNode(PGChildNodeView): setattr(server, config_param_map[arg], data[arg]) idx += 1 + if idx == 0: return make_json_response( success=0, @@ -502,6 +516,7 @@ class ServerNode(PGChildNodeView): 'id': server.id, 'name': server.name, 'host': server.host, + 'hostaddr': server.hostaddr, 'port': server.port, 'db': server.maintenance_db, 'username': server.username, @@ -532,6 +547,7 @@ class ServerNode(PGChildNodeView): request.data, encoding='utf-8' ) + for arg in required_args: if arg not in data: return make_json_response( @@ -542,6 +558,16 @@ class ServerNode(PGChildNodeView): ) ) + if 'hostaddr' in data and data['hostaddr'] != '': + try: + ipaddress.ip_address(data['hostaddr']) + except ValueError as e: + return make_json_response( + success=0, + status=400, + errormsg=gettext('Host address not valid') + ) + server = None try: @@ -550,6 +576,7 @@ class ServerNode(PGChildNodeView): servergroup_id=data[u'gid'] if u'gid' in data else gid, name=data[u'name'], host=data[u'host'], + hostaddr=data[u'hostaddr'] if u'hostaddr' in data else None, port=data[u'port'], maintenance_db=data[u'db'], username=data[u'username'], diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js index cf7dfbc..e85123c 100644 --- a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js @@ -597,6 +597,7 @@ define('pgadmin.node.server', [ name: '', sslmode: 'prefer', host: '', + hostaddr: '', port: 5432, db: 'postgres', username: '{{ username }}', @@ -649,6 +650,9 @@ define('pgadmin.node.server', [ id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'), mode: ['properties', 'edit', 'create'], disabled: 'isConnected' },{ + id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'), + mode: ['properties', 'edit', 'create'], disabled: 'isConnected' + },{ id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'), mode: ['properties', 'edit', 'create'], disabled: 'isConnected', min: 1024, max: 65535 },{ @@ -695,26 +699,52 @@ define('pgadmin.node.server', [ var check_for_empty = function(id, msg) { var v = self.get(id); if ( - _.isUndefined(v) || String(v).replace(/^\s+|\s+$/g, '') == '' + _.isUndefined(v) || v === null || String(v).replace(/^\s+|\s+$/g, '') == '' ) { err[id] = msg; errmsg = errmsg || msg; + return true; + } else { + self.errorModel.unset(id); + return false; + } + } + var check_for_valid_ipv6 = function(val){ + return /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/.test(val.trim()); + } + var check_for_valid_ip = function(id, msg) { + var v = self.get(id); + if ( + v && + !(/^(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}$/.test(v.trim())) + ) { + if(!check_for_valid_ipv6(v)){ + err[id] = msg; + errmsg = msg; + } } else { self.errorModel.unset(id); } } if (!self.isNew() && 'id' in self.sessAttrs) { - err['id'] = gettext('The ID cannot be changed.');; + err['id'] = gettext('The ID cannot be changed.'); errmsg = err['id']; } else { self.errorModel.unset('id'); } check_for_empty('name', gettext('Name must be specified.')); - check_for_empty( - 'host', gettext('Hostname or address must be specified.') - ); + if (check_for_empty( + 'host', gettext('Either Host name or Host address must be specified.') + ) && check_for_empty('hostaddr', gettext('Either Host name or Host address must be specified.'))){ + errmsg = errmsg || gettext('Either Host name or Host address must be specified'); + } else { + errmsg = undefined; + delete err['host']; + delete err['hostaddr']; + } + check_for_empty( 'db', gettext('Maintenance database must be specified.') ); @@ -722,7 +752,10 @@ define('pgadmin.node.server', [ 'username', gettext('Username must be specified.') ); check_for_empty( - 'port', '{{ _('Port must be specified.') }}' + 'port', gettext('Port must be specified.') + ); + check_for_valid_ip( + 'hostaddr', gettext('Host address must be valid IPv4 or IPv6 address.') ); this.errorModel.set(err); diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index f7db567..3dcc0a2 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -20,7 +20,6 @@ things: from flask_security import UserMixin, RoleMixin from flask_sqlalchemy import SQLAlchemy - ########################################################################## # # The schema version is used to track when upgrades are needed to the @@ -108,6 +107,7 @@ class Server(db.Model): ) name = db.Column(db.String(128), nullable=False) host = db.Column(db.String(128), nullable=False) + hostaddr = db.Column(db.String(128), nullable=True) port = db.Column( db.Integer(), db.CheckConstraint('port >= 1024 AND port <= 65534'), @@ -128,6 +128,8 @@ class Server(db.Model): backref=db.backref('server', cascade="all, delete-orphan"), lazy='joined') + + class ModulePreference(db.Model): """Define a preferences table for any modules.""" __tablename__ = 'module_preference' diff --git a/web/pgadmin/utils/driver/psycopg2/__init__.py b/web/pgadmin/utils/driver/psycopg2/__init__.py index 876b887..16342d4 100644 --- a/web/pgadmin/utils/driver/psycopg2/__init__.py +++ b/web/pgadmin/utils/driver/psycopg2/__init__.py @@ -316,6 +316,7 @@ class Connection(BaseConnection): pg_conn = psycopg2.connect( host=mgr.host, + hostaddr=mgr.hostaddr, port=mgr.port, database=database, user=user, @@ -1106,6 +1107,7 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id} try: pg_conn = psycopg2.connect( host=mgr.host, + hostaddr=mgr.hostaddr, port=mgr.port, database=self.db, user=mgr.user, @@ -1373,6 +1375,7 @@ Failed to reset the connection to the server due to following error: try: pg_conn = psycopg2.connect( host=self.manager.host, + hostaddr=self.manager.hostaddr, port=self.manager.port, database=self.db, user=self.manager.user, @@ -1519,6 +1522,7 @@ class ServerManager(object): self.sid = server.id self.host = server.host + self.hostaddr = server.hostaddr self.port = server.port self.db = server.maintenance_db self.did = None