mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-01-24 15:26:46 -06:00
Add support for the hostaddr connection parameter. This helps us play nicely with Kerberos/SSPI and friends. Fixes #2191
This commit is contained in:
parent
3f4781cdcb
commit
15cb9fc35b
BIN
docs/en_US/images/server_advanced.png
Normal file
BIN
docs/en_US/images/server_advanced.png
Normal file
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 *Cancel* button to exit without saving work.
|
||||||
* Click the *Reset* button to restore configuration parameters.
|
* 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
|
35
web/migrations/versions/3c1e4b6eda55_.py
Normal file
35
web/migrations/versions/3c1e4b6eda55_.py
Normal file
@ -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 simplejson as json
|
||||||
|
import re
|
||||||
import pgadmin.browser.server_groups as sg
|
import pgadmin.browser.server_groups as sg
|
||||||
from flask import render_template, request, make_response, jsonify, \
|
from flask import render_template, request, make_response, jsonify, \
|
||||||
current_app, url_for
|
current_app, url_for
|
||||||
@ -211,6 +211,26 @@ class ServerNode(PGChildNodeView):
|
|||||||
'delete': 'pause_wal_replay', 'put': 'resume_wal_replay'
|
'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):
|
def nodes(self, gid):
|
||||||
res = []
|
res = []
|
||||||
@ -353,6 +373,7 @@ class ServerNode(PGChildNodeView):
|
|||||||
config_param_map = {
|
config_param_map = {
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'host': 'host',
|
'host': 'host',
|
||||||
|
'hostaddr': 'hostaddr',
|
||||||
'port': 'port',
|
'port': 'port',
|
||||||
'db': 'maintenance_db',
|
'db': 'maintenance_db',
|
||||||
'username': 'username',
|
'username': 'username',
|
||||||
@ -378,13 +399,24 @@ class ServerNode(PGChildNodeView):
|
|||||||
request.data, encoding='utf-8'
|
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)
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||||
conn = manager.connection()
|
conn = manager.connection()
|
||||||
connected = conn.connected()
|
connected = conn.connected()
|
||||||
|
|
||||||
if connected:
|
if connected:
|
||||||
for arg in (
|
for arg in (
|
||||||
'host', 'port', 'db', 'username', 'sslmode', 'role'
|
'host', 'hostaddr', 'port', 'db', 'username', 'sslmode', 'role'
|
||||||
):
|
):
|
||||||
if arg in data:
|
if arg in data:
|
||||||
return forbidden(
|
return forbidden(
|
||||||
@ -501,6 +533,7 @@ class ServerNode(PGChildNodeView):
|
|||||||
'id': server.id,
|
'id': server.id,
|
||||||
'name': server.name,
|
'name': server.name,
|
||||||
'host': server.host,
|
'host': server.host,
|
||||||
|
'hostaddr': server.hostaddr,
|
||||||
'port': server.port,
|
'port': server.port,
|
||||||
'db': server.maintenance_db,
|
'db': server.maintenance_db,
|
||||||
'username': server.username,
|
'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
|
server = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -549,6 +591,7 @@ class ServerNode(PGChildNodeView):
|
|||||||
servergroup_id=data[u'gid'] if u'gid' in data else gid,
|
servergroup_id=data[u'gid'] if u'gid' in data else gid,
|
||||||
name=data[u'name'],
|
name=data[u'name'],
|
||||||
host=data[u'host'],
|
host=data[u'host'],
|
||||||
|
hostaddr=data[u'hostaddr'] if u'hostaddr' in data else None,
|
||||||
port=data[u'port'],
|
port=data[u'port'],
|
||||||
maintenance_db=data[u'db'],
|
maintenance_db=data[u'db'],
|
||||||
username=data[u'username'],
|
username=data[u'username'],
|
||||||
|
@ -602,6 +602,7 @@ define('pgadmin.node.server', [
|
|||||||
name: '',
|
name: '',
|
||||||
sslmode: 'prefer',
|
sslmode: 'prefer',
|
||||||
host: '',
|
host: '',
|
||||||
|
hostaddr: '',
|
||||||
port: 5432,
|
port: 5432,
|
||||||
db: 'postgres',
|
db: 'postgres',
|
||||||
username: current_user.name,
|
username: current_user.name,
|
||||||
@ -650,6 +651,9 @@ define('pgadmin.node.server', [
|
|||||||
},{
|
},{
|
||||||
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
|
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
|
||||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
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'),
|
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
|
||||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', min: 1024, max: 65535
|
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 check_for_empty = function(id, msg) {
|
||||||
var v = self.get(id);
|
var v = self.get(id);
|
||||||
if (
|
if (
|
||||||
_.isUndefined(v) || String(v).replace(/^\s+|\s+$/g, '') == ''
|
_.isUndefined(v) || v === null || String(v).replace(/^\s+|\s+$/g, '') == ''
|
||||||
) {
|
) {
|
||||||
err[id] = msg;
|
err[id] = msg;
|
||||||
errmsg = errmsg || 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 {
|
} else {
|
||||||
self.errorModel.unset(id);
|
self.errorModel.unset(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!self.isNew() && 'id' in self.sessAttrs) {
|
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'];
|
errmsg = err['id'];
|
||||||
} else {
|
} else {
|
||||||
self.errorModel.unset('id');
|
self.errorModel.unset('id');
|
||||||
}
|
}
|
||||||
check_for_empty('name', gettext('Name must be specified.'));
|
check_for_empty('name', gettext('Name must be specified.'));
|
||||||
|
|
||||||
check_for_empty(
|
if (check_for_empty(
|
||||||
'host', gettext('Hostname or address must be specified.')
|
'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(
|
check_for_empty(
|
||||||
'db', gettext('Maintenance database must be specified.')
|
'db', gettext('Maintenance database must be specified.')
|
||||||
);
|
);
|
||||||
@ -724,6 +771,9 @@ define('pgadmin.node.server', [
|
|||||||
'username', gettext('Username must be specified.')
|
'username', gettext('Username must be specified.')
|
||||||
);
|
);
|
||||||
check_for_empty('port', gettext('Port 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);
|
this.errorModel.set(err);
|
||||||
|
|
||||||
if (_.size(err)) {
|
if (_.size(err)) {
|
||||||
|
@ -108,6 +108,7 @@ class Server(db.Model):
|
|||||||
)
|
)
|
||||||
name = db.Column(db.String(128), nullable=False)
|
name = db.Column(db.String(128), nullable=False)
|
||||||
host = 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(
|
port = db.Column(
|
||||||
db.Integer(),
|
db.Integer(),
|
||||||
db.CheckConstraint('port >= 1024 AND port <= 65534'),
|
db.CheckConstraint('port >= 1024 AND port <= 65534'),
|
||||||
@ -128,6 +129,8 @@ class Server(db.Model):
|
|||||||
backref=db.backref('server', cascade="all, delete-orphan"),
|
backref=db.backref('server', cascade="all, delete-orphan"),
|
||||||
lazy='joined')
|
lazy='joined')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ModulePreference(db.Model):
|
class ModulePreference(db.Model):
|
||||||
"""Define a preferences table for any modules."""
|
"""Define a preferences table for any modules."""
|
||||||
__tablename__ = 'module_preference'
|
__tablename__ = 'module_preference'
|
||||||
|
@ -316,6 +316,7 @@ class Connection(BaseConnection):
|
|||||||
|
|
||||||
pg_conn = psycopg2.connect(
|
pg_conn = psycopg2.connect(
|
||||||
host=mgr.host,
|
host=mgr.host,
|
||||||
|
hostaddr=mgr.hostaddr,
|
||||||
port=mgr.port,
|
port=mgr.port,
|
||||||
database=database,
|
database=database,
|
||||||
user=user,
|
user=user,
|
||||||
@ -1106,6 +1107,7 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
|||||||
try:
|
try:
|
||||||
pg_conn = psycopg2.connect(
|
pg_conn = psycopg2.connect(
|
||||||
host=mgr.host,
|
host=mgr.host,
|
||||||
|
hostaddr=mgr.hostaddr,
|
||||||
port=mgr.port,
|
port=mgr.port,
|
||||||
database=self.db,
|
database=self.db,
|
||||||
user=mgr.user,
|
user=mgr.user,
|
||||||
@ -1373,6 +1375,7 @@ Failed to reset the connection to the server due to following error:
|
|||||||
try:
|
try:
|
||||||
pg_conn = psycopg2.connect(
|
pg_conn = psycopg2.connect(
|
||||||
host=self.manager.host,
|
host=self.manager.host,
|
||||||
|
hostaddr=self.manager.hostaddr,
|
||||||
port=self.manager.port,
|
port=self.manager.port,
|
||||||
database=self.db,
|
database=self.db,
|
||||||
user=self.manager.user,
|
user=self.manager.user,
|
||||||
@ -1519,6 +1522,7 @@ class ServerManager(object):
|
|||||||
|
|
||||||
self.sid = server.id
|
self.sid = server.id
|
||||||
self.host = server.host
|
self.host = server.host
|
||||||
|
self.hostaddr = server.hostaddr
|
||||||
self.port = server.port
|
self.port = server.port
|
||||||
self.db = server.maintenance_db
|
self.db = server.maintenance_db
|
||||||
self.did = None
|
self.did = None
|
||||||
|
Loading…
Reference in New Issue
Block a user