1) Added support for setting PostgreSQL connection parameters. #4728

2) Fixed an issue where Kerberos authentication to the server is not imported/exported. #5732
3) Increase the length of the value column of the setting table. #5746
4) Upgrade Flask-Migrate to 4.0.0. #5525
This commit is contained in:
Akshay Joshi 2023-01-23 17:19:59 +05:30 committed by GitHub
parent 91049445dd
commit a7cf698d09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 594 additions and 616 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@ -15,10 +15,12 @@ Supported Database Servers
New features
************
| `Issue #4728 <https://github.com/pgadmin-org/pgadmin4/issues/4728>`_ - Added support for setting PostgreSQL connection parameters.
Housekeeping
************
| `Issue #5525 <https://github.com/pgadmin-org/pgadmin4/issues/5525>`_ - Upgrade Flask-Migrate to 4.x.
| `Issue #5723 <https://github.com/pgadmin-org/pgadmin4/issues/5723>`_ - Improve performance by removing signal-based zoom-in, zoom-out, etc functionality from the runtime environment.
Bug fixes
@ -26,4 +28,6 @@ Bug fixes
| `Issue #5567 <https://github.com/pgadmin-org/pgadmin4/issues/5567>`_ - Fix orphan database connections resulting in an inability to connect to databases.
| `Issue #5705 <https://github.com/pgadmin-org/pgadmin4/issues/5705>`_ - Ensure that all parts of the application recommend and enforce the same length of passwords.
| `Issue #5732 <https://github.com/pgadmin-org/pgadmin4/issues/5732>`_ - Fixed an issue where Kerberos authentication to the server is not imported/exported.
| `Issue #5751 <https://github.com/pgadmin-org/pgadmin4/issues/5751>`_ - Fix failing import servers CLI due to vulnerability fix.
| `Issue #5746 <https://github.com/pgadmin-org/pgadmin4/issues/5746>`_ - Increase the length of the value column of the setting table.

View File

@ -82,17 +82,30 @@ Use the fields in the *Connection* tab to configure a connection:
see
`Section 33.16 of the Postgres documentation <https://www.postgresql.org/docs/current/libpq-pgservice.html>`_.
Click the *SSL* tab to continue.
Click the *Parameters* tab to continue.
.. image:: images/server_ssl.png
.. image:: images/server_parameters.png
:alt: Server dialog ssl tab
:align: center
Use the fields in the *SSL* tab to configure SSL:
Use the fields in the *Parameters* tab to configure a connection:
* Use the drop-down list box in the *SSL* field to select the type of SSL
connection the server should use. For more information about using SSL
encryption, see
Click on the *+* button to add a new parameter. Some of the parameters are:
* *Host address* using this field to specify the host IP address may save time
by avoiding a DNS lookup on connection, but 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.
* *Password File* field to specify the location of a password file
(.pgpass). A .pgpass file allows a user to login without providing a password
when they connect. For more information, see
`Section 33.15 of the Postgres documentation <https://www.postgresql.org/docs/current/libpq-pgpass.html>`_.
* *Connection timeout* field to specify the maximum wait for connection,
in seconds. Zero or not specified means wait indefinitely. It is not
recommended to use a timeout of less than 2 seconds. By default it is set to
10 seconds.
* *SSL mode* field to select the type of SSL connection the server should use.
For more information about using SSL encryption, see
`Section 33.18 of the Postgres documentation <https://www.postgresql.org/docs/current/libpq-ssl.html>`_.
If pgAdmin is installed in Server mode (the default mode), you can use the
@ -100,27 +113,27 @@ platform-specific File manager dialog to upload files that support SSL
encryption to the server. To access the File manager dialog, click the
icon that is located to the right of each of the following fields.
* Use the *Client certificate* field to specify the file containing the client
* *Client certificate* field to specify the file containing the client
SSL certificate. This file will replace the default
*~/.postgresql/postgresql.crt* if pgAdmin is installed in Desktop mode, and
*<STORAGE_DIR>/<USERNAME>/.postgresql/postgresql.crt* if pgAdmin is installed
in Web mode. This parameter is ignored if an SSL connection is not made.
* Use the *Client certificate key* field to specify the file containing the
* *Client certificate key* field to specify the file containing the
secret key used for the client certificate. This file will replace the
default *~/.postgresql/postgresql.key* if pgAdmin is installed in Desktop
mode, and *<STORAGE_DIR>/<USERNAME>/.postgresql/postgresql.key* if pgAdmin
is installed in Web mode. This parameter is ignored if an SSL connection is
not made.
* Use the *Root certificate* field to specify the file containing the SSL
* *Root certificate* field to specify the file containing the SSL
certificate authority. This file will replace the default
*~/.postgresql/root.crt*. This parameter is ignored if an SSL connection is
not made.
* Use the *Certificate revocation list* field to specify the file containing
* *Certificate revocation list* field to specify the file containing
the SSL certificate revocation list. This list will replace the default list,
found in *~/.postgresql/root.crl*. This parameter is ignored if an SSL
connection is not made.
* When *SSL compression?* is set to *True*, data sent over SSL connections will
be compressed. The default value is *False* (compression is disabled). This
* *SSL compression?* is set to *True*, data sent over SSL connections will
be compressed. The default value is *False* (compression is disabled). This
parameter is ignored if an SSL connection is not made.
.. warning:: In Server mode, certificates, private keys, and the revocation list
@ -175,20 +188,11 @@ Click the *Advanced* tab to continue.
Use the fields in the *Advanced* tab to configure a connection:
* Specify the IP address of the server host in the *Host address* field. Using
this field to specify the host IP address may save time by avoiding a DNS
lookup on connection, but 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.
* Use the *DB restriction* field to provide a SQL restriction that will be used
against the pg_database table to limit the databases that you see. For
example, you might enter: *live_db test_db* so that only live_db and test_db
are shown in the pgAdmin browser. Separate entries with a comma or tab as you
type.
* Use the *Password File* field to specify the location of a password file
(.pgpass). A .pgpass file allows a user to login without providing a password
when they connect. For more information, see
`Section 33.15 of the Postgres documentation <https://www.postgresql.org/docs/current/libpq-pgpass.html>`_.
* Use the *Password exec command* field to specify a shell command to be executed
to retrieve a password to be used for SQL authentication. The ``stdout`` of the
command will be used as the SQL password. This may be useful when the password
@ -199,10 +203,6 @@ Use the fields in the *Advanced* tab to configure a connection:
the password will not expire until your pgAdmin session does.
Zero means the command will be executed for each new connection or reconnection that is made.
If the generated password is not valid indefinitely, set this value to slightly before it will expire.
* Use the *Connection timeout* field to specify the maximum wait for connection,
in seconds. Zero or not specified means wait indefinitely. It is not
recommended to use a timeout of less than 2 seconds. By default it is set to
10 seconds.
.. note:: The password file option is only supported when pgAdmin is using libpq
v10.0 or later to connect to the server.

View File

@ -13,7 +13,7 @@ Flask==2.1.*; python_version >= '3.7'
Flask-Gravatar==0.*
Flask-Login==0.*
Flask-Mail==0.*
Flask-Migrate==3.*
Flask-Migrate==4.*
dnspython==2.2.1
greenlet==1.1.2; python_version <= '3.10'
Flask-SQLAlchemy==2.5.*

View File

@ -0,0 +1,99 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
""" Used for connection parameter changes in the server table.
Revision ID: f656e56dfdc8
Revises: f79844e926ae
Create Date: 2023-01-02 14:52:48.109290
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = 'f656e56dfdc8'
down_revision = 'f79844e926ae'
branch_labels = None
depends_on = None
def migrate_connection_params(table_name):
"""
This function is used to add connection parameter as JSON data and drop
unused columns.
"""
op.add_column(table_name,
sa.Column('connection_params', sa.JSON()))
# define table representation
meta = sa.MetaData(bind=op.get_bind())
meta.reflect(only=(table_name,))
server_table = sa.Table(table_name, meta)
# Create a select statement
stmt = sa.select([
server_table.columns.id, server_table.columns.ssl_mode,
server_table.columns.sslcert, server_table.columns.sslkey,
server_table.columns.sslrootcert, server_table.columns.sslcrl,
server_table.columns.sslcompression, server_table.columns.hostaddr,
server_table.columns.passfile, server_table.columns.connect_timeout
])
# Fetch the data from the server table
results = op.get_bind().execute(stmt).fetchall()
for rows in results:
connection_params = {}
server_id = 0
for key, value in rows.items():
if key == 'id':
server_id = value
# Name is changed from ssl_mode to sslmode
if key == 'ssl_mode':
key = 'sslmode'
if value is not None and key != 'id':
connection_params[key] = value
# Update the newly added column with JSON data.
op.execute(
server_table.update().where(server_table.columns.id == server_id)
.values(connection_params=connection_params)
)
# Drop unused columns
with op.batch_alter_table(table_name) as batch_op:
if table_name == 'server':
batch_op.drop_constraint('ck_ssl_mode')
batch_op.drop_column('ssl_mode')
batch_op.drop_column('sslcert')
batch_op.drop_column('sslkey')
batch_op.drop_column('sslrootcert')
batch_op.drop_column('sslcrl')
batch_op.drop_column('sslcompression')
batch_op.drop_column('hostaddr')
batch_op.drop_column('passfile')
batch_op.drop_column('connect_timeout')
def upgrade():
migrate_connection_params('server')
migrate_connection_params('sharedserver')
# Increasing the length of the value column of the setting table.
with op.batch_alter_table("setting") as batch_op:
batch_op.alter_column('value',
existing_type=sa.String(length=1024),
type_=sa.String(length=2048),
existing_nullable=False)
def downgrade():
# pgAdmin only upgrades, downgrade not implemented.
pass

View File

@ -608,7 +608,8 @@ def create_app(app_name=None):
port=port,
maintenance_db='postgres',
username=superuser,
ssl_mode='prefer',
connection_params={'sslmode': 'prefer',
'connect_timeout': 10},
comment=comment,
discovery_id=discovery_id)

View File

@ -157,9 +157,19 @@ class ServerModule(sg.ServerGroupPluginModule):
server.tunnel_username = sharedserver.tunnel_username
server.tunnel_password = sharedserver.tunnel_password
server.save_password = sharedserver.save_password
server.passfile = sharedserver.passfile
if hasattr(server, 'connection_params') and \
hasattr(sharedserver, 'connection_params') and \
'passfile' in server.connection_params and \
'passfile' in sharedserver.connection_params:
server.connection_params['passfile'] = \
sharedserver.connection_params['passfile']
server.servergroup_id = sharedserver.servergroup_id
server.sslcert = sharedserver.sslcert
if hasattr(server, 'connection_params') and \
hasattr(sharedserver, 'connection_params') and \
'sslcert' in server.connection_params and \
'sslcert' in sharedserver.connection_params:
server.connection_params['sslcert'] = \
sharedserver.connection_params['sslcert']
server.username = sharedserver.username
server.server_owner = sharedserver.server_owner
server.password = sharedserver.password
@ -346,29 +356,23 @@ class ServerModule(sg.ServerGroupPluginModule):
servergroup_id=gid,
name=data.name,
host=data.host,
hostaddr=data.hostaddr,
port=data.port,
maintenance_db=data.maintenance_db,
username=None,
save_password=0,
ssl_mode=data.ssl_mode,
comment=None,
role=data.role,
sslcert=None,
sslkey=None,
sslrootcert=None,
sslcrl=None,
bgcolor=data.bgcolor if data.bgcolor else None,
fgcolor=data.fgcolor if data.fgcolor else None,
service=data.service if data.service else None,
connect_timeout=0,
use_ssh_tunnel=data.use_ssh_tunnel,
tunnel_host=data.tunnel_host,
tunnel_port=22,
tunnel_username=None,
tunnel_authentication=0,
tunnel_identity_file=None,
shared=True
shared=True,
connection_params=data.connection_params
)
db.session.add(shared_server)
db.session.commit()
@ -486,11 +490,53 @@ class ServerNode(PGChildNodeView):
)
data[field] = dummy_ssl_file
# For Desktop mode, we will allow to default
else:
data[field] = None
return flag, data
def convert_connection_parameter(self, params):
"""
This function is used to convert the connection parameter based
on the instance type.
"""
conn_params = None
# if params is of type list then it is coming from the frontend,
# and we have to convert it into the dict and store it into the
# database
if isinstance(params, list):
conn_params = {}
for item in params:
conn_params[item['name']] = item['value']
# if params is of type dict then it is coming from the database,
# and we have to convert it into the list of params to show on GUI.
elif isinstance(params, dict):
conn_params = []
for key, value in params.items():
if value is not None:
conn_params.append(
{'name': key, 'keyword': key, 'value': value})
return conn_params
def update_connection_parameter(self, data, server):
"""
This function is used to update the connection parameters.
"""
if 'connection_params' in data and \
hasattr(server, 'connection_params'):
existing_conn_params = getattr(server, 'connection_params')
new_conn_params = data['connection_params']
if 'deleted' in new_conn_params:
for item in new_conn_params['deleted']:
del existing_conn_params[item['name']]
if 'added' in new_conn_params:
for item in new_conn_params['added']:
existing_conn_params[item['name']] = item['value']
if 'changed' in new_conn_params:
for item in new_conn_params['changed']:
existing_conn_params[item['name']] = item['value']
data['connection_params'] = existing_conn_params
@login_required
def nodes(self, gid):
res = []
@ -701,27 +747,18 @@ class ServerNode(PGChildNodeView):
config_param_map = {
'name': 'name',
'host': 'host',
'hostaddr': 'hostaddr',
'port': 'port',
'db': 'maintenance_db',
'username': 'username',
'sslmode': 'ssl_mode',
'gid': 'servergroup_id',
'comment': 'comment',
'role': 'role',
'db_res': 'db_res',
'passfile': 'passfile',
'passexec_cmd': 'passexec_cmd',
'passexec_expiration': 'passexec_expiration',
'sslcert': 'sslcert',
'sslkey': 'sslkey',
'sslrootcert': 'sslrootcert',
'sslcrl': 'sslcrl',
'sslcompression': 'sslcompression',
'bgcolor': 'bgcolor',
'fgcolor': 'fgcolor',
'service': 'service',
'connect_timeout': 'connect_timeout',
'use_ssh_tunnel': 'use_ssh_tunnel',
'tunnel_host': 'tunnel_host',
'tunnel_port': 'tunnel_port',
@ -730,15 +767,14 @@ class ServerNode(PGChildNodeView):
'tunnel_identity_file': 'tunnel_identity_file',
'shared': 'shared',
'kerberos_conn': 'kerberos_conn',
'connection_params': 'connection_params'
}
disp_lbl = {
'name': gettext('name'),
'hostaddr': gettext('Host name/address'),
'port': gettext('Port'),
'db': gettext('Maintenance database'),
'username': gettext('Username'),
'sslmode': gettext('SSL Mode'),
'comment': gettext('Comments'),
'role': gettext('Role')
}
@ -750,12 +786,16 @@ class ServerNode(PGChildNodeView):
if 'db_res' in data:
data['db_res'] = ','.join(data['db_res'])
hostaddr = data.get('hostaddr')
if hostaddr and not is_valid_ipaddress(hostaddr):
# Update connection parameter if any.
self.update_connection_parameter(data, server)
if 'connection_params' in data and \
'hostaddr' in data['connection_params'] and \
not is_valid_ipaddress(data['connection_params']['hostaddr']):
return make_json_response(
success=0,
status=400,
errormsg=gettext('Host address not valid')
errormsg=gettext('Not a valid Host address')
)
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
@ -849,8 +889,7 @@ class ServerNode(PGChildNodeView):
if connected:
for arg in (
'hostaddr', 'db', 'sslmode',
'role', 'service'
'db', 'role', 'service'
):
if arg in data:
return forbidden(
@ -911,10 +950,6 @@ class ServerNode(PGChildNodeView):
def properties(self, gid, sid):
"""Return list of attributes of a server"""
sslcert = None
sslkey = None
sslrootcert = None
sslcrl = None
server = Server.query.filter_by(
id=sid).first()
@ -941,19 +976,13 @@ class ServerNode(PGChildNodeView):
shared_server)
server_owner = server.server_owner
is_ssl = True if server.ssl_mode in self.SSL_MODES else False
if is_ssl:
sslcert = server.sslcert
sslkey = server.sslkey
sslrootcert = server.sslrootcert
sslcrl = server.sslcrl
use_ssh_tunnel = 0
tunnel_host = None
tunnel_port = 22
tunnel_username = None
tunnel_authentication = 0
connection_params = \
self.convert_connection_parameter(server.connection_params)
if server.use_ssh_tunnel:
use_ssh_tunnel = server.use_ssh_tunnel
@ -968,7 +997,6 @@ class ServerNode(PGChildNodeView):
'server_owner': server_owner,
'user_id': server.user_id,
'host': server.host,
'hostaddr': server.hostaddr,
'port': server.port,
'db': server.maintenance_db,
'shared': server.shared if config.SERVER_MODE else None,
@ -979,26 +1007,16 @@ class ServerNode(PGChildNodeView):
'role': server.role,
'connected': connected,
'version': manager.ver,
'sslmode': server.ssl_mode,
'server_type': manager.server_type if connected else 'pg',
'bgcolor': server.bgcolor,
'fgcolor': server.fgcolor,
'db_res': server.db_res.split(',') if server.db_res else None,
'passfile': server.passfile if server.passfile else None,
'passexec_cmd':
server.passexec_cmd if server.passexec_cmd else None,
'passexec_expiration':
server.passexec_expiration if server.passexec_expiration
else None,
'sslcert': sslcert,
'sslkey': sslkey,
'sslrootcert': sslrootcert,
'sslcrl': sslcrl,
'sslcompression': True if is_ssl and server.sslcompression
else False,
'service': server.service if server.service else None,
'connect_timeout':
server.connect_timeout if server.connect_timeout else 0,
'use_ssh_tunnel': use_ssh_tunnel,
'tunnel_host': tunnel_host,
'tunnel_port': tunnel_port,
@ -1009,7 +1027,9 @@ class ServerNode(PGChildNodeView):
'kerberos_conn': bool(server.kerberos_conn),
'gss_authenticated': manager.gss_authenticated,
'gss_encrypted': manager.gss_encrypted,
'cloud_status': server.cloud_status
'cloud_status': server.cloud_status,
'connection_params': connection_params,
'connection_string': manager.connection_string
}
return ajax_response(response)
@ -1017,11 +1037,7 @@ class ServerNode(PGChildNodeView):
@login_required
def create(self, gid):
"""Add a server node to the settings database"""
required_args = [
'name',
'db',
'sslmode',
]
required_args = ['name', 'db']
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
@ -1057,8 +1073,11 @@ class ServerNode(PGChildNodeView):
).format(arg)
)
hostaddr = data.get('hostaddr')
if hostaddr and not is_valid_ipaddress(data['hostaddr']):
connection_params = self.convert_connection_parameter(
data.get('connection_params', []))
if 'hostaddr' in connection_params and \
not is_valid_ipaddress(connection_params['hostaddr']):
return make_json_response(
success=0,
status=400,
@ -1066,7 +1085,10 @@ class ServerNode(PGChildNodeView):
)
# To check ssl configuration
is_ssl, data = self.check_ssl_fields(data)
is_ssl, connection_params = self.check_ssl_fields(connection_params)
# set the connection params again in the data
if 'connection_params' in data:
data['connection_params'] = connection_params
server = None
@ -1076,26 +1098,18 @@ class ServerNode(PGChildNodeView):
servergroup_id=data.get('gid', gid),
name=data.get('name'),
host=data.get('host', None),
hostaddr=hostaddr,
port=data.get('port'),
maintenance_db=data.get('db', None),
username=data.get('username'),
save_password=1 if data.get('save_password', False) and
config.ALLOW_SAVE_PASSWORD else 0,
ssl_mode=data.get('sslmode'),
comment=data.get('comment', None),
role=data.get('role', None),
db_res=','.join(data['db_res'])
if 'db_res' in data else None,
sslcert=data.get('sslcert', None),
sslkey=data.get('sslkey', None),
sslrootcert=data.get('sslrootcert', None),
sslcrl=data.get('sslcrl', None),
sslcompression=1 if is_ssl and data['sslcompression'] else 0,
bgcolor=data.get('bgcolor', None),
fgcolor=data.get('fgcolor', None),
service=data.get('service', None),
connect_timeout=data.get('connect_timeout', 0),
use_ssh_tunnel=1 if data.get('use_ssh_tunnel', False) else 0,
tunnel_host=data.get('tunnel_host', None),
tunnel_port=data.get('tunnel_port', 22),
@ -1104,10 +1118,10 @@ class ServerNode(PGChildNodeView):
False) else 0,
tunnel_identity_file=data.get('tunnel_identity_file', None),
shared=data.get('shared', None),
passfile=data.get('passfile', None),
passexec_cmd=data.get('passexec_cmd', None),
passexec_expiration=data.get('passexec_expiration', None),
kerberos_conn=1 if data.get('kerberos_conn', False) else 0,
connection_params=connection_params
)
db.session.add(server)
db.session.commit()
@ -1131,10 +1145,9 @@ class ServerNode(PGChildNodeView):
have_password = True
password = data['password']
password = encrypt(password, crypt_key)
elif 'passfile' in data and data["passfile"] != '':
elif 'passfile' in data['connection_params'] and \
data['connection_params']['passfile'] != '':
passfile = data['passfile']
setattr(server, 'passfile', passfile)
db.session.commit()
if 'tunnel_password' in data and data["tunnel_password"] != '':
have_tunnel_password = True
@ -1391,14 +1404,20 @@ class ServerNode(PGChildNodeView):
return internal_server_error(errormsg=str(e))
if 'password' not in data and (server.kerberos_conn is False or
server.kerberos_conn is None):
passfile_param = None
if hasattr(server, 'connection_params') and \
'passfile' in server.connection_params:
passfile_param = server.connection_params['passfile']
conn_passwd = getattr(conn, 'password', None)
if conn_passwd is None and not server.save_password and \
server.passfile is None and \
passfile_param is None and \
server.passexec_cmd is None and \
server.service is None:
prompt_password = True
elif server.passfile and server.passfile != '':
passfile = server.passfile
elif passfile_param and passfile_param != '':
passfile = passfile_param
else:
password = conn_passwd or server.password
else:
@ -1657,8 +1676,11 @@ class ServerNode(PGChildNodeView):
# If there is no password found for the server
# then check for pgpass file
if not server.password and not manager.password and \
server.passfile and manager.passfile and \
server.passfile == manager.passfile:
hasattr(server, 'connection_params') and \
'passfile' in server.connection_params and \
manager.get_connection_param_value('passfile') and \
server.connection_params['passfile'] == \
manager.get_connection_param_value('passfile'):
is_passfile = True
# Check for password only if there is no pgpass file used
@ -1859,8 +1881,11 @@ class ServerNode(PGChildNodeView):
)
if (not server.password or not manager.password) and \
server.passfile and manager.passfile and \
server.passfile == manager.passfile:
hasattr(server, 'connection_params') and \
'passfile' in server.connection_params and \
manager.get_connection_param_value('passfile') and \
server.connection_params['passfile'] == \
manager.get_connection_param_value('passfile'):
is_pgpass = True
return make_json_response(
success=1,

View File

@ -2252,9 +2252,10 @@ class MViewNode(ViewNode, VacuumSettings):
manager.export_password_env(p.id)
# Check for connection timeout and if it is greater than 0
# then set the environment variable PGCONNECT_TIMEOUT.
if manager.connect_timeout > 0:
timeout = manager.get_connection_param_value('connect_timeout')
if timeout and timeout > 0:
env = dict()
env['PGCONNECT_TIMEOUT'] = str(manager.connect_timeout)
env['PGCONNECT_TIMEOUT'] = str(timeout)
p.set_env_variables(server, env=env)
else:
p.set_env_variables(server)

View File

@ -29,6 +29,7 @@ define('pgadmin.node.server', [
type: 'server',
dialogHelp: url_for('help.static', {'filename': 'server_dialog.html'}),
label: gettext('Server'),
width: pgBrowser.stdW.md + 'px',
canDrop: function(node){
let serverOwner = node.user_id;
return !(serverOwner != current_user.id && !_.isUndefined(serverOwner));

View File

@ -9,15 +9,12 @@
import gettext from 'sources/gettext';
import _ from 'lodash';
import {Address4, Address6} from 'ip-address';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import pgAdmin from 'sources/pgadmin';
import {default as supportedServers} from 'pgadmin.server.supported_servers';
import current_user from 'pgadmin.user_management.current_user';
import { isEmptyString } from 'sources/validators';
import VariableSchema from './variable.ui';
export default class ServerSchema extends BaseUISchema {
constructor(serverGroupOptions=[], userId=0, initValues={}) {
@ -27,9 +24,7 @@ export default class ServerSchema extends BaseUISchema {
name: '',
bgcolor: '',
fgcolor: '',
sslmode: 'prefer',
host: '',
hostaddr: '',
port: 5432,
db: 'postgres',
username: current_user.name,
@ -38,14 +33,8 @@ export default class ServerSchema extends BaseUISchema {
password: undefined,
save_password: false,
db_res: [],
passfile: undefined,
passexec: undefined,
passexec_expiration: undefined,
sslcompression: false,
sslcert: undefined,
sslkey: undefined,
sslrootcert: undefined,
sslcrl: undefined,
service: undefined,
use_ssh_tunnel: 0,
tunnel_host: undefined,
@ -55,16 +44,22 @@ export default class ServerSchema extends BaseUISchema {
tunnel_password: undefined,
tunnel_authentication: false,
save_tunnel_password: false,
connect_timeout: 10,
connection_string: undefined,
connection_params: [
{'name': 'sslmode', 'value': 'prefer', 'keyword': 'sslmode'},
{'name': 'connect_timeout', 'value': 10, 'keyword': 'connect_timeout'}],
...initValues,
});
this.serverGroupOptions = serverGroupOptions;
this.paramSchema = new VariableSchema(this.getConnectionParameters(), null, null, ['name', 'keyword', 'value']);
this.userId = userId;
_.bindAll(this, 'isShared', 'isSSL');
_.bindAll(this, 'isShared');
}
get SSL_MODES() { return ['prefer', 'require', 'verify-ca', 'verify-full']; }
initialise(state) {
this.paramSchema.setAllReadOnly(this.isConnected(state));
}
isShared(state) {
return !this.isNew(state) && this.userId != current_user.id && state.shared;
@ -74,16 +69,6 @@ export default class ServerSchema extends BaseUISchema {
return Boolean(state.connected);
}
isSSL(state) {
return this.SSL_MODES.indexOf(state.sslmode) == -1;
}
isValidLib() {
// older version of libpq do not support 'passfile' parameter in
// connect method, valid libpq must have version >= 100000
return pgAdmin.Browser.utils.pg_libpq_version < 100000;
}
get baseFields() {
let obj = this;
return [
@ -148,8 +133,10 @@ export default class ServerSchema extends BaseUISchema {
{
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'],
},
{
}, {
id: 'connection_string', label: gettext('Connection String'), type: 'multiline',
group: gettext('Connection'), mode: ['properties'], readonly: true,
}, {
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: obj.isShared,
depChange: (state)=>{
@ -228,103 +215,13 @@ export default class ServerSchema extends BaseUISchema {
id: 'service', label: gettext('Service'), type: 'text',
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
group: gettext('Connection'),
},
{
id: 'sslmode', label: gettext('SSL mode'), type: 'select', group: gettext('SSL'),
controlProps: {
allowClear: false,
},
mode: ['properties', 'edit', 'create'], disabled: obj.isConnected,
options: [
{label: gettext('Allow'), value: 'allow'},
{label: gettext('Prefer'), value: 'prefer'},
{label: gettext('Require'), value: 'require'},
{label: gettext('Disable'), value: 'disable'},
{label: gettext('Verify-CA'), value: 'verify-ca'},
{label: gettext('Verify-Full'), value: 'verify-full'},
],
},
{
id: 'sslcert', label: gettext('Client certificate'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},
{
id: 'sslkey', label: gettext('Client certificate key'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},
{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['edit', 'create'], group: gettext('SSL'),
disabled: obj.isSSL, readonly: obj.isConnected,
deps: ['sslmode'],
},
{
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslcert = state.sslcert;
return !_.isUndefined(sslcert) && !_.isNull(sslcert);
},
},{
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslkey = state.sslkey;
return !_.isUndefined(sslkey) && !_.isNull(sslkey);
},
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslrootcert = state.sslrootcert;
return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
},
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslcrl = state.sslcrl;
return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
},
},{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['properties'], group: gettext('SSL'),
deps: ['sslmode'],
visible: function(state) {
return _.indexOf(obj.SSL_MODES, state.sslmode) != -1;
},
},
{
}, {
id: 'connection_params', label: gettext('Connection Parameters'),
type: 'collection', group: gettext('Parameters'),
schema: this.paramSchema, mode: ['edit', 'create'], uniqueCol: ['name'],
canAdd: (state)=> !obj.isConnected(state), canEdit: false,
canDelete: (state)=> !obj.isConnected(state),
}, {
id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
disabled: function() {
@ -401,9 +298,6 @@ export default class ServerSchema extends BaseUISchema {
disabled: function(state) {
return (!current_user.allow_save_tunnel_password || !state.use_ssh_tunnel);
},
}, {
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
},
{
id: 'db_res', label: gettext('DB restriction'), type: 'select', group: gettext('Advanced'),
@ -411,22 +305,6 @@ export default class ServerSchema extends BaseUISchema {
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected, controlProps: {
multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: 'Specify the databases to be restrict...'},
},
{
id: 'passfile', label: gettext('Password file'), type: 'file',
group: gettext('Advanced'), mode: ['edit', 'create'],
disabled: obj.isValidLib, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
},
{
id: 'passfile', label: gettext('Password file'), type: 'text',
group: gettext('Advanced'), mode: ['properties'],
visible: function(state) {
let passfile = state.passfile;
return !_.isUndefined(passfile) && !_.isNull(passfile);
},
},
{
id: 'passexec_cmd', label: gettext('Password exec command'), type: 'text',
group: gettext('Advanced'),
@ -439,12 +317,6 @@ export default class ServerSchema extends BaseUISchema {
visible: function(state) {
return !_.isEmpty(state.passexec_cmd);
},
},
{
id: 'connect_timeout', label: gettext('Connection timeout (seconds)'),
type: 'int', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
min: 0,
}
];
}
@ -453,30 +325,12 @@ export default class ServerSchema extends BaseUISchema {
let errmsg = null;
if (isEmptyString(state.service)) {
errmsg = gettext('Either Host name, Address or Service must be specified.');
if(isEmptyString(state.host) && isEmptyString(state.hostaddr)) {
errmsg = gettext('Either Host name or Service must be specified.');
if(isEmptyString(state.host)) {
setError('host', errmsg);
return true;
} else {
setError('host', null);
setError('hostaddr', null);
}
/* IP address validate */
if (state.hostaddr) {
try {
new Address4(state.hostaddr);
} catch(e) {
try {
new Address6(state.hostaddr);
} catch(ex) {
errmsg = gettext('Host address must be valid IPv4 or IPv6 address.');
setError('hostaddr', errmsg);
return true;
}
}
} else {
setError('hostaddr', null);
}
/* Hostname, IP address validate */
@ -507,7 +361,7 @@ export default class ServerSchema extends BaseUISchema {
setError('port', null);
}
} else {
_.each(['host', 'hostaddr', 'db', 'username', 'port'], (item) => {
_.each(['host', 'db', 'username', 'port'], (item) => {
setError(item, null);
});
}
@ -549,4 +403,92 @@ export default class ServerSchema extends BaseUISchema {
}
return false;
}
getConnectionParameters() {
return [{
'value': 'hostaddr', 'label': gettext('Host address'), 'vartype': 'string'
}, {
'value': 'passfile', 'label': gettext('Password file'), 'vartype': 'file'
}, {
'value': 'channel_binding', 'label': gettext('Channel binding'), 'vartype': 'enum',
'enumvals': [gettext('prefer'), gettext('require'), gettext('disable')],
'min_server_version': '13'
}, {
'value': 'connect_timeout', 'label': gettext('Connection timeout (seconds)'), 'vartype': 'integer'
}, {
'value': 'client_encoding', 'label': gettext('Client encoding'), 'vartype': 'string'
}, {
'value': 'options', 'label': gettext('Options'), 'vartype': 'string'
}, {
'value': 'application_name', 'label': gettext('Application name'), 'vartype': 'string'
}, {
'value': 'fallback_application_name', 'label': gettext('Fallback application name'), 'vartype': 'string'
}, {
'value': 'keepalives', 'label': gettext('Keepalives'), 'vartype': 'integer'
}, {
'value': 'keepalives_idle', 'label': gettext('Keepalives idle (seconds)'), 'vartype': 'integer'
}, {
'value': 'keepalives_interval', 'label': gettext('Keepalives interval (seconds)'), 'vartype': 'integer'
}, {
'value': 'keepalives_count', 'label': gettext('Keepalives count'), 'vartype': 'integer'
}, {
'value': 'tcp_user_timeout', 'label': gettext('TCP user timeout (milliseconds)'), 'vartype': 'integer',
'min_server_version': '12'
}, {
'value': 'tty', 'label': gettext('TTY'), 'vartype': 'string',
'max_server_version': '13'
}, {
'value': 'replication', 'label': gettext('Replication'), 'vartype': 'enum',
'enumvals': [gettext('on'), gettext('off'), gettext('database')],
'min_server_version': '11'
}, {
'value': 'gssencmode', 'label': gettext('GSS encmode'), 'vartype': 'enum',
'enumvals': [gettext('prefer'), gettext('require'), gettext('disable')],
'min_server_version': '12'
}, {
'value': 'sslmode', 'label': gettext('SSL mode'), 'vartype': 'enum',
'enumvals': [gettext('allow'), gettext('prefer'), gettext('require'),
gettext('disable'), gettext('verify-ca'), gettext('verify-full')]
}, {
'value': 'sslcompression', 'label': gettext('SSL compression?'), 'vartype': 'bool',
}, {
'value': 'sslcert', 'label': gettext('Client certificate'), 'vartype': 'file'
}, {
'value': 'sslkey', 'label': gettext('Client certificate key'), 'vartype': 'file'
}, {
'value': 'sslpassword', 'label': gettext('SSL password'), 'vartype': 'string',
'min_server_version': '13'
}, {
'value': 'sslrootcert', 'label': gettext('Root certificate'), 'vartype': 'file'
}, {
'value': 'sslcrl', 'label': gettext('Certificate revocation list'), 'vartype': 'file',
}, {
'value': 'sslcrldir', 'label': gettext('Certificate revocation list directory'), 'vartype': 'file',
'min_server_version': '14'
}, {
'value': 'sslsni', 'label': gettext('Server name indication'), 'vartype': 'bool',
'min_server_version': '14'
}, {
'value': 'requirepeer', 'label': gettext('Require peer'), 'vartype': 'string',
}, {
'value': 'ssl_min_protocol_version', 'label': gettext('SSL min protocol version'),
'vartype': 'enum', 'min_server_version': '13',
'enumvals': [gettext('TLSv1'), gettext('TLSv1.1'), gettext('TLSv1.2'),
gettext('TLSv1.3')]
}, {
'value': 'ssl_max_protocol_version', 'label': gettext('SSL max protocol version'),
'vartype': 'enum', 'min_server_version': '13',
'enumvals': [gettext('TLSv1'), gettext('TLSv1.1'), gettext('TLSv1.2'),
gettext('TLSv1.3')]
}, {
'value': 'krbsrvname', 'label': gettext('Kerberos service name'), 'vartype': 'string',
}, {
'value': 'gsslib', 'label': gettext('GSS library'), 'vartype': 'string',
}, {
'value': 'target_session_attrs', 'label': gettext('Target session attribute'),
'vartype': 'enum',
'enumvals': [gettext('any'), gettext('read-write'), gettext('read-only'),
gettext('primary'), gettext('standby'), gettext('prefer-standby')]
}];
}
}

View File

@ -51,12 +51,18 @@ export default class VariableSchema extends BaseUISchema {
value: undefined,
role: null,
database: null,
keyword: null,
});
this.vnameOptions = vnameOptions;
this.databaseOptions = databaseOptions;
this.roleOptions = roleOptions;
this.varTypes = {};
this.keys = keys;
this.allReadOnly = false;
}
setAllReadOnly(isReadOnly) {
this.allReadOnly = isReadOnly;
}
setVarTypes(options) {
@ -67,6 +73,19 @@ export default class VariableSchema extends BaseUISchema {
});
}
getPlaceHolderMsg(variable) {
let msg = '';
if (variable?.min_server_version && variable?.max_server_version) {
msg = gettext('%s <= Supported version >= %s', variable?.max_server_version, variable?.min_server_version);
} else if (variable?.min_server_version) {
msg = gettext('Supported version >= %s', variable?.min_server_version);
} else if (variable?.max_server_version) {
msg = gettext('Supported version <= %s', variable?.max_server_version);
}
return msg;
}
getValueFieldProps(variable) {
switch(variable?.vartype) {
case 'bool':
@ -74,17 +93,44 @@ export default class VariableSchema extends BaseUISchema {
case 'enum':
return {
cell: 'select',
options: (variable.enumvals || []).map((val)=>({
options: (variable.enumvals || []).map((val)=>(typeof(val)=='string' ? {
label: val,
value: val
}))
}: val)),
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'integer':
return 'int';
return {
cell: 'int',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'real':
return 'numeric';
return {
cell: 'numeric',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'string':
return 'text';
return {
cell: 'text',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'file':
return {
cell: 'file',
controlProps: {
dialogType: 'select_file',
supportedTypes: ['*'],
placeholder: this.getPlaceHolderMsg(variable)
}
};
default:
return '';
}
@ -99,8 +145,8 @@ export default class VariableSchema extends BaseUISchema {
},
{
id: 'name', label: gettext('Name'), type:'text',
readonly: function(state) {
return !obj.isNew(state);
editable: function(state) {
return obj.isNew(state) || !obj.allReadOnly;
},
cell: ()=>({
cell: 'select',
@ -109,9 +155,16 @@ export default class VariableSchema extends BaseUISchema {
controlProps: { allowClear: false },
}),
},
{
id: 'keyword', label: gettext('Keyword'), type: '', cell: '',
deps: ['name'], minWidth: 25,
depChange: (state, source, topState, actionObj)=>{
return { keyword: actionObj.value };
}
},
{
id: 'value', label: gettext('Value'), type: 'text',
deps: ['name'],
deps: ['name'], editable: !obj.allReadOnly,
depChange: (state, source)=>{
if(source[source.length-1] == 'name') {
let variable = this.varTypes[state.name];

View File

@ -207,21 +207,6 @@
"status_code": 200
}
},
{
"name": "Add server with advanced properties",
"url": "/browser/server/obj/",
"is_positive_test": true,
"owner_server": true,
"test_data": {
"passfile": "test.pgpass",
"hostaddr": "127.0.0.1"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Add server with background/foreground color",
"url": "/browser/server/obj/",
@ -805,22 +790,6 @@
"status_code": 410
}
},
{
"name": "Update server with incorrect hostaddr",
"url": "/browser/server/obj/",
"is_positive_test": true,
"test_data": {
"comment": "PLACE_HOLDER",
"hostaddr": "PLACE_HOLDER",
"db_res": "PLACE_HOLDER",
"id": "PLACE_HOLDER"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 400
}
},
{
"name": "update a server , make server shared",
"url": "/browser/server/obj/",
@ -938,21 +907,6 @@
"status_code": 200
}
},
{
"name": "update advanced properties of server",
"url": "/browser/server/obj/",
"is_positive_test": true,
"owner_server": true,
"test_data": {
"passfile": "test_01.pgpass",
"hostaddr": "127.0.0.1"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "remove ssl properties from server",
"url": "/browser/server/obj/",
@ -972,21 +926,6 @@
"status_code": 200
}
},
{
"name": "remove advanced properties from server",
"url": "/browser/server/obj/",
"is_positive_test": true,
"owner_server": true,
"test_data": {
"passfile": "",
"hostaddr": ""
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Update server with background/foreground color",
"url": "/browser/server/obj/",
@ -1046,22 +985,6 @@
"status_code": 410
}
},
{
"name": "Update server with incorrect hostaddr",
"url": "/browser/server/obj/",
"is_positive_test": true,
"test_data": {
"comment": "PLACE_HOLDER",
"hostaddr": "PLACE_HOLDER",
"db_res": "PLACE_HOLDER",
"id": "PLACE_HOLDER"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 400
}
},
{
"name": "update a server , make server shared",
"url": "/browser/server/obj/",

View File

@ -20,11 +20,11 @@ def create_server(server, SERVER_GROUP):
server['shared'] = False
server_details = (1, SERVER_GROUP, server['name'], server['host'],
server['port'], server['db'], server['username'],
server['role'], server['sslmode'], server['comment'],
server['role'], server['comment'],
server['shared'])
cur.execute('INSERT INTO server (user_id, servergroup_id, name, host, '
'port, maintenance_db, username, role, ssl_mode,'
' comment, shared) VALUES (?,?,?,?,?,?,?,?,?,?,?)',
'port, maintenance_db, username, role,'
' comment, shared) VALUES (?,?,?,?,?,?,?,?,?,?)',
server_details)
server_id = cur.lastrowid
conn.commit()

View File

@ -808,13 +808,22 @@ class BatchProcess():
"""Set environment variables"""
if server:
# Set SSL related ENV variables
if server.sslcert and server.sslkey and server.sslrootcert:
if hasattr(server, 'connection_params') and \
server.connection_params and \
'sslcert' in server.connection_params and \
'sslkey' in server.connection_params and \
'sslrootcert' in server.connection_params:
# SSL environment variables
sslcert = get_complete_file_path(server.sslcert)
sslkey = get_complete_file_path(server.sslkey)
sslrootcert = get_complete_file_path(server.sslrootcert)
sslcert = get_complete_file_path(
server.connection_params['sslcert'])
sslkey = get_complete_file_path(
server.connection_params['sslkey'])
sslrootcert = get_complete_file_path(
server.connection_params['sslrootcert'])
self.env['PGSSLMODE'] = server.ssl_mode
self.env['PGSSLMODE'] = server.connection_params['sslmode'] \
if hasattr(server, 'connection_params') and \
'sslmode' in server.connection_params else 'prefer'
self.env['PGSSLCERT'] = '' if sslcert is None else sslcert
self.env['PGSSLKEY'] = '' if sslkey is None else sslkey
self.env['PGSSLROOTCERT'] = \

View File

@ -48,9 +48,8 @@ def _create_server(data):
name=data.get('name'),
maintenance_db=data.get('db'),
username=data.get('username'),
ssl_mode='prefer',
cloud_status=data.get('cloud_status'),
connect_timeout=30,
connection_params={'sslmode': 'prefer', 'connect_timeout': 30}
)
db.session.add(server)

View File

@ -20,8 +20,10 @@ things:
from flask_security import UserMixin, RoleMixin
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.mutable import MutableDict
import sqlalchemy.types as types
import uuid
import json
##########################################################################
#
@ -31,7 +33,7 @@ import uuid
#
##########################################################################
SCHEMA_VERSION = 34
SCHEMA_VERSION = 35
##########################################################################
#
@ -71,6 +73,25 @@ class PgAdminDbBinaryString(types.TypeDecorator):
return value
class PgAdminJSONString(types.TypeDecorator):
"""
This function is used to return a string representing a json object from
an object and vise versa.
"""
impl = types.String
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
class Version(db.Model):
"""Version numbers for reference/upgrade purposes"""
__tablename__ = 'version'
@ -149,7 +170,6 @@ class Server(db.Model):
)
name = db.Column(db.String(128), nullable=False)
host = db.Column(db.String(128), nullable=True)
hostaddr = db.Column(db.String(128), nullable=True)
port = db.Column(
db.Integer(),
db.CheckConstraint('port >= 1 AND port <= 65534'),
@ -163,13 +183,6 @@ class Server(db.Model):
nullable=False
)
role = db.Column(db.String(64), nullable=True)
ssl_mode = db.Column(
db.String(16),
db.CheckConstraint(
"ssl_mode IN ('allow', 'prefer', 'require', 'disable', "
"'verify-ca', 'verify-full')"
),
nullable=False)
comment = db.Column(db.String(1024), nullable=True)
discovery_id = db.Column(db.String(128), nullable=True)
servers = db.relationship(
@ -178,22 +191,11 @@ class Server(db.Model):
lazy='joined'
)
db_res = db.Column(db.Text(), nullable=True)
passfile = db.Column(db.Text(), nullable=True)
passexec_cmd = db.Column(db.Text(), nullable=True)
passexec_expiration = db.Column(db.Integer(), 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
)
bgcolor = db.Column(db.String(10), nullable=True)
fgcolor = db.Column(db.String(10), nullable=True)
service = db.Column(db.Text(), nullable=True)
connect_timeout = db.Column(db.Integer(), nullable=False)
use_ssh_tunnel = db.Column(
db.Integer(),
db.CheckConstraint('use_ssh_tunnel >= 0 AND use_ssh_tunnel <= 1'),
@ -216,6 +218,7 @@ class Server(db.Model):
shared = db.Column(db.Boolean(), nullable=False)
kerberos_conn = db.Column(db.Boolean(), nullable=False, default=0)
cloud_status = db.Column(db.Integer(), nullable=False, default=0)
connection_params = db.Column(MutableDict.as_mutable(PgAdminJSONString))
@property
def serialize(self):
@ -226,35 +229,27 @@ class Server(db.Model):
"servergroup_id": self.servergroup_id,
"name": self.name,
"host": self.host,
"hostaddr": self.hostaddr,
"port": self.port,
"maintenance_db": self.maintenance_db,
"username": self.username,
"password": self.password,
"save_password": self.save_password,
"role": self.role,
"ssl_mode": self.ssl_mode,
"comment": self.comment,
"discovery_id": self.discovery_id,
"db_res": self.db_res,
"passfile": self.passfile,
"passexec_cmd": self.passexec_cmd,
"passexec_expiration": self.passexec_expiration,
"sslcert": self.sslcert,
"sslkey": self.sslkey,
"sslrootcert": self.sslrootcert,
"sslcrl": self.sslcrl,
"sslcompression": self.sslcompression,
"bgcolor": self.bgcolor,
"fgcolor": self.fgcolor,
"service": self.service,
"connect_timeout": self.connect_timeout,
"use_ssh_tunnel": self.use_ssh_tunnel,
"tunnel_host": self.tunnel_host,
"tunnel_port": self.tunnel_port,
"tunnel_authentication": self.tunnel_authentication,
"tunnel_identity_file": self.tunnel_identity_file,
"tunnel_password": self.tunnel_password
"tunnel_password": self.tunnel_password,
"connection_params": self.connection_params
}
@ -420,7 +415,6 @@ class SharedServer(db.Model):
)
name = db.Column(db.String(128), nullable=False)
host = db.Column(db.String(128), nullable=True)
hostaddr = db.Column(db.String(128), nullable=True)
port = db.Column(
db.Integer(),
nullable=True)
@ -433,13 +427,6 @@ class SharedServer(db.Model):
nullable=False
)
role = db.Column(db.String(64), nullable=True)
ssl_mode = db.Column(
db.String(16),
db.CheckConstraint(
"ssl_mode IN ('allow', 'prefer', 'require', 'disable', "
"'verify-ca', 'verify-full')"
),
nullable=False)
comment = db.Column(db.String(1024), nullable=True)
discovery_id = db.Column(db.String(128), nullable=True)
servers = db.relationship(
@ -448,20 +435,9 @@ class SharedServer(db.Model):
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
)
bgcolor = db.Column(db.String(10), nullable=True)
fgcolor = db.Column(db.String(10), nullable=True)
service = db.Column(db.Text(), nullable=True)
connect_timeout = db.Column(db.Integer(), nullable=False)
use_ssh_tunnel = db.Column(
db.Integer(),
db.CheckConstraint('use_ssh_tunnel >= 0 AND use_ssh_tunnel <= 1'),
@ -482,6 +458,7 @@ class SharedServer(db.Model):
tunnel_identity_file = db.Column(db.String(64), nullable=True)
tunnel_password = db.Column(PgAdminDbBinaryString())
shared = db.Column(db.Boolean(), nullable=False)
connection_params = db.Column(MutableDict.as_mutable(PgAdminJSONString))
class Macros(db.Model):

View File

@ -405,9 +405,10 @@ def create_backup_objects_job(sid):
manager.export_password_env(p.id)
# Check for connection timeout and if it is greater than 0 then
# set the environment variable PGCONNECT_TIMEOUT.
if manager.connect_timeout > 0:
timeout = manager.get_connection_param_value('connect_timeout')
if timeout and timeout > 0:
env = dict()
env['PGCONNECT_TIMEOUT'] = str(manager.connect_timeout)
env['PGCONNECT_TIMEOUT'] = str(timeout)
p.set_env_variables(server, env=env)
else:
p.set_env_variables(server)

View File

@ -251,9 +251,10 @@ def create_maintenance_job(sid, did):
manager.export_password_env(p.id)
# Check for connection timeout and if it is greater than 0 then
# set the environment variable PGCONNECT_TIMEOUT.
if manager.connect_timeout > 0:
timeout = manager.get_connection_param_value('connect_timeout')
if timeout and timeout > 0:
env = dict()
env['PGCONNECT_TIMEOUT'] = str(manager.connect_timeout)
env['PGCONNECT_TIMEOUT'] = str(timeout)
p.set_env_variables(server, env=env)
else:
p.set_env_variables(server)

View File

@ -377,68 +377,19 @@ def get_connection_str(psql_utility, db, manager):
:param db: database name to connect specific db.
:return: connection attribute list for PSQL connection.
"""
conn_attr = get_conn_str_win(manager, db)
manager.export_password_env('PGPASSWORD')
db = db.replace('"', '\\"')
db = db.replace("'", "\\'")
database = db if db != '' else 'postgres'
user = underscore_unescape(manager.user) if manager.user else 'postgres'
conn_attr = manager.create_connection_string(database, user)
conn_attr_list = list()
conn_attr_list.append(psql_utility)
conn_attr_list.append(conn_attr)
return conn_attr_list
def get_conn_str_win(manager, db):
"""
Get connection attributes for psql connection.
:param manager:
:param db:
:return:
"""
manager.export_password_env('PGPASSWORD')
db = db.replace('"', '\\"')
db = db.replace("'", "\\'")
conn_attr =\
'host=\'{0}\' port=\'{1}\' dbname=\'{2}\' user=\'{3}\' ' \
'sslmode=\'{4}\' sslcompression=\'{5}\' ' \
''.format(
manager.local_bind_host if manager.use_ssh_tunnel else
manager.host,
manager.local_bind_port if manager.use_ssh_tunnel else
manager.port,
db if db != '' else 'postgres',
underscore_unescape(manager.user) if manager.user else 'postgres',
manager.ssl_mode,
True if manager.sslcompression else False,
)
if manager.hostaddr:
conn_attr = " {0} hostaddr='{1}'".format(conn_attr, manager.hostaddr)
if manager.passfile:
conn_attr = " {0} passfile='{1}'".format(conn_attr,
get_complete_file_path(
manager.passfile))
if get_complete_file_path(manager.sslcert):
conn_attr = " {0} sslcert='{1}'".format(
conn_attr, get_complete_file_path(manager.sslcert))
if get_complete_file_path(manager.sslkey):
conn_attr = " {0} sslkey='{1}'".format(
conn_attr, get_complete_file_path(manager.sslkey))
if get_complete_file_path(manager.sslrootcert):
conn_attr = " {0} sslrootcert='{1}'".format(
conn_attr, get_complete_file_path(manager.sslrootcert))
if get_complete_file_path(manager.sslcrl):
conn_attr = " {0} sslcrl='{1}'".format(
conn_attr, get_complete_file_path(manager.sslcrl))
if manager.service:
conn_attr = " {0} service='{1}'".format(
conn_attr, get_complete_file_path(manager.service))
return conn_attr
def enter_key_press(data):
"""
Handel the Enter key press event.

View File

@ -382,9 +382,10 @@ def create_restore_job(sid):
manager.export_password_env(p.id)
# Check for connection timeout and if it is greater than 0 then
# set the environment variable PGCONNECT_TIMEOUT.
if manager.connect_timeout > 0:
timeout = manager.get_connection_param_value('connect_timeout')
if timeout and timeout > 0:
env = dict()
env['PGCONNECT_TIMEOUT'] = str(manager.connect_timeout)
env['PGCONNECT_TIMEOUT'] = str(timeout)
p.set_env_variables(server, env=env)
else:
p.set_env_variables(server)

View File

@ -441,31 +441,26 @@ def dump_database_servers(output_file, selected_servers,
add_value(attr_dict, "Name", server.name)
add_value(attr_dict, "Group", group_name)
add_value(attr_dict, "Host", server.host)
add_value(attr_dict, "HostAddr", server.hostaddr)
add_value(attr_dict, "Port", server.port)
add_value(attr_dict, "MaintenanceDB", server.maintenance_db)
add_value(attr_dict, "Username", server.username)
add_value(attr_dict, "Role", server.role)
add_value(attr_dict, "SSLMode", server.ssl_mode)
add_value(attr_dict, "Comment", server.comment)
add_value(attr_dict, "Shared", server.shared)
add_value(attr_dict, "DBRestriction", server.db_res)
add_value(attr_dict, "PassFile", server.passfile)
add_value(attr_dict, "SSLCert", server.sslcert)
add_value(attr_dict, "SSLKey", server.sslkey)
add_value(attr_dict, "SSLRootCert", server.sslrootcert)
add_value(attr_dict, "SSLCrl", server.sslcrl)
add_value(attr_dict, "SSLCompression", server.sslcompression)
add_value(attr_dict, "BGColor", server.bgcolor)
add_value(attr_dict, "FGColor", server.fgcolor)
add_value(attr_dict, "Service", server.service)
add_value(attr_dict, "Timeout", server.connect_timeout)
add_value(attr_dict, "UseSSHTunnel", server.use_ssh_tunnel)
add_value(attr_dict, "TunnelHost", server.tunnel_host)
add_value(attr_dict, "TunnelPort", server.tunnel_port)
add_value(attr_dict, "TunnelUsername", server.tunnel_username)
add_value(attr_dict, "TunnelAuthentication",
server.tunnel_authentication)
add_value(attr_dict, "KerberosAuthentication",
server.kerberos_conn),
add_value(attr_dict, "ConnectionParameters",
server.connection_params)
servers_dumped = servers_dumped + 1
@ -549,14 +544,12 @@ def validate_json_data(data, is_admin):
if errmsg:
return errmsg
for attrib in ("SSLMode", "MaintenanceDB"):
errmsg = check_attrib(attrib)
if errmsg:
return errmsg
errmsg = check_attrib("MaintenanceDB")
if errmsg:
return errmsg
if "Host" not in obj and "HostAddr" not in obj and not \
is_service_attrib_available:
return gettext("'Host', 'HostAddr' or 'Service' attribute not "
if "Host" not in obj and not is_service_attrib_available:
return gettext("'Host' or 'Service' attribute not "
"found for server '%s'" % server)
for server in skip_servers:
@ -639,36 +632,37 @@ def load_database_servers(input_file, selected_servers,
new_server.name = obj["Name"]
new_server.servergroup_id = group_id
new_server.user_id = user_id
new_server.ssl_mode = obj["SSLMode"]
new_server.maintenance_db = obj["MaintenanceDB"]
new_server.host = obj.get("Host", None)
new_server.hostaddr = obj.get("HostAddr", None)
new_server.port = obj.get("Port", None)
new_server.username = obj.get("Username", None)
new_server.role = obj.get("Role", None)
new_server.ssl_mode = obj["SSLMode"]
new_server.comment = obj.get("Comment", None)
new_server.db_res = obj.get("DBRestriction", None)
new_server.passfile = obj.get("PassFile", None)
if 'ConnectionParameters' in obj:
new_server.connection_params = \
obj.get("ConnectionParameters", None)
else:
# JSON file format is old before introduction of the
# connection parameters.
conn_param = dict()
for item in ['HostAddr', 'SSLMode', 'PassFile', 'SSLCert',
'SSLKey', 'SSLRootCert', 'SSLCrl', 'Timeout',
'SSLCompression']:
if item in obj:
key = item.lower()
if item == 'Timeout':
key = 'connect_timeout'
conn_param[key] = obj.get(item)
new_server.sslcert = obj.get("SSLCert", None)
new_server.sslkey = obj.get("SSLKey", None)
new_server.sslrootcert = obj.get("SSLRootCert", None)
new_server.sslcrl = obj.get("SSLCrl", None)
new_server.sslcompression = obj.get("SSLCompression", None)
new_server.connection_params = conn_param
new_server.bgcolor = obj.get("BGColor", None)
@ -676,8 +670,6 @@ def load_database_servers(input_file, selected_servers,
new_server.service = obj.get("Service", None)
new_server.connect_timeout = obj.get("Timeout", None)
new_server.use_ssh_tunnel = obj.get("UseSSHTunnel", None)
new_server.tunnel_host = obj.get("TunnelHost", None)
@ -689,8 +681,9 @@ def load_database_servers(input_file, selected_servers,
new_server.tunnel_authentication = \
obj.get("TunnelAuthentication", None)
new_server.shared = \
obj.get("Shared", None)
new_server.shared = obj.get("Shared", None)
new_server.kerberos_conn = obj.get("KerberosAuthentication", None)
db.session.add(new_server)

View File

@ -254,8 +254,6 @@ class Connection(BaseConnection):
else:
return True, None
pg_conn = None
passfile = None
manager = self.manager
crypt_key_present, crypt_key = get_crypt_key()
@ -299,7 +297,7 @@ class Connection(BaseConnection):
# we will check for pgpass file availability from connection manager
# if it's present then we will use it
if not password and not encpass and not passfile:
passfile = manager.passfile if manager.passfile else None
passfile = manager.get_connection_param_value('passfile')
if manager.passexec:
password = manager.passexec.get()
@ -315,8 +313,10 @@ class Connection(BaseConnection):
os.environ['PGAPPNAME'] = '{0} - {1}'.format(
config.APP_NAME, conn_id)
ssl_key = get_complete_file_path(manager.sslkey)
if ssl_key and manager.ssl_mode in \
ssl_key = get_complete_file_path(
manager.get_connection_param_value('sslkey'))
sslmode = manager.get_connection_param_value('sslmode')
if ssl_key and sslmode in \
['require', 'verify-ca', 'verify-full']:
ssl_key_file_permission = \
int(oct(os.stat(ssl_key).st_mode)[-3:])
@ -324,27 +324,12 @@ class Connection(BaseConnection):
os.chmod(ssl_key, 0o600)
with ConnectionLocker(manager.kerberos_conn):
pg_conn = psycopg2.connect(
host=manager.local_bind_host if manager.use_ssh_tunnel
else manager.host,
hostaddr=manager.local_bind_host if manager.use_ssh_tunnel
else manager.hostaddr,
port=manager.local_bind_port if manager.use_ssh_tunnel
else manager.port,
database=database,
user=user,
password=password,
async_=self.async_,
passfile=get_complete_file_path(passfile),
sslmode=manager.ssl_mode,
sslcert=get_complete_file_path(manager.sslcert),
sslkey=ssl_key,
sslrootcert=get_complete_file_path(manager.sslrootcert),
sslcrl=get_complete_file_path(manager.sslcrl),
sslcompression=True if manager.sslcompression else False,
service=manager.service,
connect_timeout=manager.connect_timeout
)
# Create the connection string
connection_string = manager.create_connection_string(
database, user, password)
pg_conn = psycopg2.connect(connection_string,
async_=self.async_)
# If connection is asynchronous then we will have to wait
# until the connection is ready to use.
@ -1413,7 +1398,6 @@ WHERE db.datname = current_database()""")
def reset(self):
if self.conn and self.conn.closed:
self.conn = None
pg_conn = None
manager = self.manager
is_return, return_value, password = self._decrypt_password(manager)
@ -1422,26 +1406,11 @@ WHERE db.datname = current_database()""")
try:
with ConnectionLocker(manager.kerberos_conn):
pg_conn = psycopg2.connect(
host=manager.local_bind_host if manager.use_ssh_tunnel
else manager.host,
hostaddr=manager.local_bind_host if manager.use_ssh_tunnel
else manager.hostaddr,
port=manager.local_bind_port if manager.use_ssh_tunnel
else manager.port,
database=self.db,
user=manager.user,
password=password,
passfile=get_complete_file_path(manager.passfile),
sslmode=manager.ssl_mode,
sslcert=get_complete_file_path(manager.sslcert),
sslkey=get_complete_file_path(manager.sslkey),
sslrootcert=get_complete_file_path(manager.sslrootcert),
sslcrl=get_complete_file_path(manager.sslcrl),
sslcompression=True if manager.sslcompression else False,
service=manager.service,
connect_timeout=manager.connect_timeout
)
# Create the connection string
connection_string = manager.create_connection_string(
self.db, manager.user, password)
pg_conn = psycopg2.connect(connection_string)
except psycopg2.Error as e:
if e.pgerror:
@ -1725,30 +1694,12 @@ Failed to reset the connection to the server due to following error:
try:
with ConnectionLocker(self.manager.kerberos_conn):
pg_conn = psycopg2.connect(
host=self.manager.local_bind_host if
self.manager.use_ssh_tunnel else self.manager.host,
hostaddr=self.manager.local_bind_host if
self.manager.use_ssh_tunnel else
self.manager.hostaddr,
port=self.manager.local_bind_port if
self.manager.use_ssh_tunnel else self.manager.port,
database=self.db,
user=self.manager.user,
password=password,
passfile=get_complete_file_path(self.manager.passfile),
sslmode=self.manager.ssl_mode,
sslcert=get_complete_file_path(self.manager.sslcert),
sslkey=get_complete_file_path(self.manager.sslkey),
sslrootcert=get_complete_file_path(
self.manager.sslrootcert
),
sslcrl=get_complete_file_path(self.manager.sslcrl),
sslcompression=True if self.manager.sslcompression
else False,
service=self.manager.service,
connect_timeout=self.manager.connect_timeout
)
# Create the connection string
connection_string = \
self.manager.create_connection_string(
self.db, self.manager.user, password)
pg_conn = psycopg2.connect(connection_string)
# Get the cursor and run the query
cur = pg_conn.cursor()
@ -1756,7 +1707,6 @@ Failed to reset the connection to the server due to following error:
# Close the connection
pg_conn.close()
pg_conn = None
except psycopg2.Error as e:
status = False

View File

@ -49,6 +49,7 @@ class ServerManager():
self.local_bind_port = None
self.tunnel_object = None
self.tunnel_created = False
self.connection_string = ''
self.update(server)
@ -65,7 +66,6 @@ class ServerManager():
self.sid = server.id
self.host = server.host
self.hostaddr = server.hostaddr
self.port = server.port
self.db = server.maintenance_db
self.shared = server.shared
@ -73,23 +73,14 @@ class ServerManager():
self.user = server.username
self.password = server.password
self.role = server.role
self.ssl_mode = server.ssl_mode
self.pinged = datetime.datetime.now()
self.db_info = dict()
self.server_types = None
self.db_res = server.db_res
self.passfile = server.passfile
self.passexec = \
PasswordExec(server.passexec_cmd, server.passexec_expiration) \
if server.passexec_cmd else None
self.sslcert = server.sslcert
self.sslkey = server.sslkey
self.sslrootcert = server.sslrootcert
self.sslcrl = server.sslcrl
self.sslcompression = True if server.sslcompression else False
self.service = server.service
self.connect_timeout = \
server.connect_timeout if server.connect_timeout else 0
if config.SUPPORT_SSH_TUNNEL:
self.use_ssh_tunnel = server.use_ssh_tunnel
self.tunnel_host = server.tunnel_host
@ -113,6 +104,9 @@ class ServerManager():
self.kerberos_conn = server.kerberos_conn
self.gss_authenticated = False
self.gss_encrypted = False
self.connection_params = server.connection_params
self.connection_string = self.create_connection_string(self.db,
self.user)
for con in self.connections:
self.connections[con]._release()
@ -623,3 +617,53 @@ WHERE db.oid = {0}""".format(did))
self.local_bind_port = None
self.tunnel_object = None
self.tunnel_created = False
def get_connection_param_value(self, param_name):
"""
This function return the value of param_name if found in the
connection parameter.
"""
value = None
if self.connection_params and param_name in self.connection_params:
value = self.connection_params[param_name]
return value
def create_connection_string(self, database, user, password=None):
"""
This function is used to create connection string based on the
parameters.
"""
full_connection_string = \
'host=\'{0}\' port=\'{1}\' dbname=\'{2}\' user=\'{3}\''.format(
self.local_bind_host if self.use_ssh_tunnel else self.host,
self.local_bind_port if self.use_ssh_tunnel else self.port,
database, user)
# Loop through all the connection parameters set in the server dialog.
if self.connection_params and isinstance(self.connection_params, dict):
for key, value in self.connection_params.items():
# Getting complete file path if the key is one of the below.
if key in ['passfile', 'sslcert', 'sslkey', 'sslrootcert',
'sslcrl', 'service', 'sslcrldir']:
value = get_complete_file_path(value)
# In case of host address need to check ssh tunnel flag.
if key == 'hostaddr':
value = self.local_bind_host if self.use_ssh_tunnel else \
value
full_connection_string = \
"{0} {1}='{2}'".format(full_connection_string, key, value)
# Password should not be visible into the connection string, so
# setting the class variable with password to 'xxxxxxx'.
if password:
self.connection_string = "{0} password='xxxxxxx'".format(
full_connection_string)
if password:
full_connection_string = "{0} password='{1}'".format(
full_connection_string, password)
return full_connection_string

View File

@ -54,14 +54,9 @@ describe('ServerSchema', ()=>{
let setError = jasmine.createSpy('setError');
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('host', 'Either Host name, Address or Service must be specified.');
state.hostaddr = 'incorrectip';
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('hostaddr', 'Host address must be valid IPv4 or IPv6 address.');
expect(setError).toHaveBeenCalledWith('host', 'Either Host name or Service must be specified.');
state.host = '127.0.0.1';
state.hostaddr = null;
schemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('username', 'Username must be specified.');

View File

@ -74,9 +74,18 @@ describe('VariableSchema', ()=>{
expect(schemaObj.getValueFieldProps({vartype: 'enum', enumvals: []})).toEqual(jasmine.objectContaining({
cell: 'select',
}));
expect(schemaObj.getValueFieldProps({vartype: 'integer'})).toBe('int');
expect(schemaObj.getValueFieldProps({vartype: 'real'})).toBe('numeric');
expect(schemaObj.getValueFieldProps({vartype: 'string'})).toBe('text');
expect(schemaObj.getValueFieldProps({vartype: 'integer'})).toEqual(jasmine.objectContaining({
cell: 'int',
}));
expect(schemaObj.getValueFieldProps({vartype: 'real'})).toEqual(jasmine.objectContaining({
cell: 'numeric',
}));
expect(schemaObj.getValueFieldProps({vartype: 'string'})).toEqual(jasmine.objectContaining({
cell: 'text',
}));
expect(schemaObj.getValueFieldProps({vartype: 'file'})).toEqual(jasmine.objectContaining({
cell: 'file',
}));
expect(schemaObj.getValueFieldProps({})).toBe('');
});

View File

@ -611,10 +611,10 @@ def create_server(server):
cur = conn.cursor()
server_details = (1, SERVER_GROUP, server['name'], server['host'],
server['port'], server['db'], server['username'],
server['role'], server['sslmode'], server['comment'])
server['role'], server['comment'])
cur.execute('INSERT INTO server (user_id, servergroup_id, name, host, '
'port, maintenance_db, username, role, ssl_mode,'
' comment) VALUES (?,?,?,?,?,?,?,?,?,?)', server_details)
'port, maintenance_db, username, role,'
' comment) VALUES (?,?,?,?,?,?,?,?,?)', server_details)
server_id = cur.lastrowid
conn.commit()
conn.close()
@ -776,7 +776,7 @@ def get_db_server(sid):
cur = conn.cursor()
server = cur.execute(
'SELECT name, host, port, maintenance_db,'
' username, ssl_mode FROM server where id=%s' % sid
' username FROM server where id=%s' % sid
)
server = server.fetchone()
if server:
@ -785,14 +785,13 @@ def get_db_server(sid):
db_port = server[2]
db_name = server[3]
username = server[4]
ssl_mode = server[5]
config_servers = test_setup.config_data['server_credentials']
# Get the db password from config file for appropriate server
db_password = get_db_password(config_servers, name, host, db_port)
if db_password:
# Drop database
connection = get_db_connection(
db_name, username, db_password, host, db_port, ssl_mode
db_name, username, db_password, host, db_port
)
conn.close()
return connection