Add support for the hostaddr connection parameter. This helps us play nicely with Kerberos/SSPI and friends. Fixes #2191
parent
3f4781cdcb
commit
15cb9fc35b
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
|
@ -38,3 +38,10 @@ Use the fields in the *Connection* tab to configure a connection:
|
|||
* Click the *Cancel* button to exit without saving work.
|
||||
* Click the *Reset* button to restore configuration parameters.
|
||||
|
||||
Click the *Advanced* tab to continue.
|
||||
|
||||
.. image:: images/server_advanced.png
|
||||
|
||||
Use the fields in the *Advanced* tab to configure a connection:
|
||||
|
||||
* Specify the IP address of the server host. Using this field to specify the host IP address will avoid a DNS lookup on connection, however it may be useful to specify both a host name and address when using Kerberos, GSSAPI, or SSPI authentication methods, as well as for verify-full SSL certificate verification
|
|
@ -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
|
|
@ -8,7 +8,7 @@
|
|||
##########################################################################
|
||||
|
||||
import simplejson as json
|
||||
|
||||
import re
|
||||
import pgadmin.browser.server_groups as sg
|
||||
from flask import render_template, request, make_response, jsonify, \
|
||||
current_app, url_for
|
||||
|
@ -211,6 +211,26 @@ class ServerNode(PGChildNodeView):
|
|||
'delete': 'pause_wal_replay', 'put': 'resume_wal_replay'
|
||||
}]
|
||||
})
|
||||
EXP_IP4 = "^\s*((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."\
|
||||
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."\
|
||||
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."\
|
||||
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s*$"
|
||||
EXP_IP6 = '^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|'\
|
||||
'(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((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})|:))|'\
|
||||
'(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|'\
|
||||
':((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})|:))|'\
|
||||
'(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((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}))|:))|'\
|
||||
'(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((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}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|'\
|
||||
'((:[0-9A-Fa-f]{1,4}){0,3}:((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}))|:))|'\
|
||||
'(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((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}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|'\
|
||||
'((:[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)
|
||||
|
||||
|
||||
def nodes(self, gid):
|
||||
res = []
|
||||
|
@ -353,6 +373,7 @@ class ServerNode(PGChildNodeView):
|
|||
config_param_map = {
|
||||
'name': 'name',
|
||||
'host': 'host',
|
||||
'hostaddr': 'hostaddr',
|
||||
'port': 'port',
|
||||
'db': 'maintenance_db',
|
||||
'username': 'username',
|
||||
|
@ -378,13 +399,24 @@ class ServerNode(PGChildNodeView):
|
|||
request.data, encoding='utf-8'
|
||||
)
|
||||
|
||||
if 'hostaddr' in data and data['hostaddr'] != '':
|
||||
if not self.pat4.match(data['hostaddr']):
|
||||
if not self.pat6.match(data['hostaddr']):
|
||||
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(
|
||||
|
@ -501,6 +533,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,
|
||||
|
@ -541,6 +574,15 @@ class ServerNode(PGChildNodeView):
|
|||
)
|
||||
)
|
||||
|
||||
if 'hostaddr' in data and data['hostaddr'] != '':
|
||||
if not self.pat4.match(data['hostaddr']):
|
||||
if not self.pat6.match(data['hostaddr']):
|
||||
return make_json_response(
|
||||
success=0,
|
||||
status=400,
|
||||
errormsg=gettext('Host address not valid')
|
||||
)
|
||||
|
||||
server = None
|
||||
|
||||
try:
|
||||
|
@ -549,6 +591,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'],
|
||||
|
|
|
@ -602,6 +602,7 @@ define('pgadmin.node.server', [
|
|||
name: '',
|
||||
sslmode: 'prefer',
|
||||
host: '',
|
||||
hostaddr: '',
|
||||
port: 5432,
|
||||
db: 'postgres',
|
||||
username: current_user.name,
|
||||
|
@ -650,6 +651,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
|
||||
|
@ -697,26 +701,69 @@ 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){
|
||||
// Regular expression for validating IPv6 address formats
|
||||
var exps = ['^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|',
|
||||
'(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((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})|:))|',
|
||||
'(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|',
|
||||
':((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})|:))|',
|
||||
'(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((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}))|:))|',
|
||||
'(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((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}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|',
|
||||
'((:[0-9A-Fa-f]{1,4}){0,3}:((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}))|:))|',
|
||||
'(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((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}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|',
|
||||
'((:[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*$'];
|
||||
|
||||
var exp = new RegExp(exps.join(''));
|
||||
return exp.test(val.trim());
|
||||
}
|
||||
var check_for_valid_ip = function(id, msg) {
|
||||
var v4exps = "(^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)";
|
||||
var v4exp = new RegExp(v4exps);
|
||||
var v = self.get(id);
|
||||
if (
|
||||
v && !(v4exp.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.')
|
||||
);
|
||||
|
@ -724,6 +771,9 @@ define('pgadmin.node.server', [
|
|||
'username', gettext('Username must be specified.')
|
||||
);
|
||||
check_for_empty('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);
|
||||
|
||||
if (_.size(err)) {
|
||||
|
|
|
@ -108,6 +108,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 +129,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'
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue