Allow selection of SSL certificates and pgpass files in connection properties. Fixes #2649. Fixes #2650
BIN
docs/en_US/images/server_advanced.png
Normal file → Executable file
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 35 KiB |
BIN
docs/en_US/images/server_connection.png
Normal file → Executable file
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 42 KiB |
BIN
docs/en_US/images/server_general.png
Normal file → Executable file
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 38 KiB |
BIN
docs/en_US/images/server_ssl.png
Executable file
After Width: | Height: | Size: 39 KiB |
@ -1,20 +1,23 @@
|
||||
.. _server_dialog:
|
||||
|
||||
*****************
|
||||
The Server Dialog
|
||||
The Server Dialog
|
||||
*****************
|
||||
|
||||
Use the *Server* dialog to describe a connection to a server. Note: you must ensure the pg_hba.conf file of the server from which you are connecting allows connections from the host of the client.
|
||||
Use the *Server* dialog to describe a connection to a server. Note: you must ensure the pg_hba.conf file of the server
|
||||
from which you are connecting allows connections from the host of the client.
|
||||
|
||||
The *Server* dialog organizes the connection of a server through the following dialog tabs: *General*, and *Connection*.
|
||||
The *Server* dialog organizes the connection of a server through the following dialog tabs: *General*, and *Connection*.
|
||||
|
||||
.. image:: images/server_general.png
|
||||
|
||||
Use the fields in the *General* tab to identify the server:
|
||||
|
||||
* Use the *Name* field to add a descriptive name for the server; the name specified will be displayed in the *pgAdmin* tree control of the client.
|
||||
* Use the *Name* field to add a descriptive name for the server; the name specified will be displayed in the *pgAdmin*
|
||||
tree control of the client.
|
||||
* Use the drop-down list box in the *Server group* field to specify the *pgAdmin* tree control parent node for the server.
|
||||
* Uncheck the checkbox next to *Connect now?* to instruct pgAdmin not to attempt a connection upon completion of the dialog. The default enables connection.
|
||||
* Uncheck the checkbox next to *Connect now?* to instruct pgAdmin not to attempt a connection upon completion of the
|
||||
dialog. The default enables connection.
|
||||
* Provide a comment about the server in the *Comments* field.
|
||||
|
||||
Click the *Connection* tab to continue.
|
||||
@ -23,26 +26,72 @@ Click the *Connection* tab to continue.
|
||||
|
||||
Use the fields in the *Connection* tab to configure a connection:
|
||||
|
||||
* Specify the IP address of the server host, or the fully qualified domain name in the *Host name/address* field. On Unix based systems, the address field may be left blank to use the default PostgreSQL Unix Domain Socket on the local machine, or may be set to an alternate path containing a PostgreSQL socket. If you enter a path, the path must begin with a "/".
|
||||
* Specify the IP address of the server host, or the fully qualified domain name in the *Host name/address* field. On
|
||||
Unix based systems, the address field may be left blank to use the default PostgreSQL Unix Domain Socket on the local
|
||||
machine, or may be set to an alternate path containing a PostgreSQL socket. If you enter a path, the path must begin
|
||||
with a "/".
|
||||
* Enter the listener port number of the server host in the *Port* field. The default is *5432*.
|
||||
* Use the *Maintenance database* field to specify the name of the initial database to which the client will connect. If you will be using pgAgent or adminpack objects, the pgAgent schema and adminpack objects should be installed on that database.
|
||||
* Use the *User name* field to specify the name of a role that will be used when authenticating with the server.
|
||||
* Use the *Password* field to provide a password that will be supplied when authenticating with the server.
|
||||
* Check the box next to *Save password* to instruct pgAdmin to save the password for future use.
|
||||
* Use the *Role* field to specify the name of a role that has privileges that will be conveyed to the client after authentication with the server. This selection allows you to connect as one role, and then assume the permissions of this specified role after the connection is established. Note that the connecting role must be a member of the role specified.
|
||||
* 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 Section 31.18 of the Postgres documentation:
|
||||
|
||||
http://www.postgresql.org/docs/9.5/static/libpq-ssl.html
|
||||
* Use the *Maintenance database* field to specify the name of the initial database to which the client will connect.
|
||||
If you will be using pgAgent or adminpack objects, the pgAgent schema and adminpack objects should be installed on that
|
||||
database.
|
||||
* Use the *User name* field to specify the name of a role that will be used when authenticating with the server.
|
||||
* Use the *Password* field to provide a password that will be supplied when authenticating with the server.
|
||||
* Check the box next to *Save password* to instruct pgAdmin to save the password for future use.
|
||||
* Use the *Role* field to specify the name of a role that has privileges that will be conveyed to the client after
|
||||
authentication with the server. This selection allows you to connect as one role, and then assume the permissions of
|
||||
this specified role after the connection is established. Note that the connecting role must be a member of the role
|
||||
specified.
|
||||
|
||||
Click the *SSL* tab to continue.
|
||||
|
||||
.. image:: images/server_ssl.png
|
||||
|
||||
Use the fields in the *SSL* tab to configure a SSL:
|
||||
|
||||
* 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 Section 31.18 of the Postgres documentation:
|
||||
|
||||
http://www.postgresql.org/docs/9.5/static/libpq-ssl.html
|
||||
|
||||
* Specify the file containing the name of the client SSL certificate, replacing the default *~/.postgresql/postgresql.crt*
|
||||
in case of Desktop mode and *<STORAGE_DIR>/<USERNAME>/.postgresql/postgresql.crt* in case of Web mode. This parameter
|
||||
is ignored if an SSL connection is not made.
|
||||
* Specify the file containing the secret key used for the client certificate, replacing the default *~/.postgresql/postgresql.key*
|
||||
in case of Desktop mode and *<STORAGE_DIR>/<USERNAME>/.postgresql/postgresql.key* in case of Web mode. This parameter
|
||||
is ignored if an SSL connection is not made.
|
||||
* Specify the file containing the SSL certificate authority, replacing the default *~/.postgresql/root.crt*. This
|
||||
parameter is ignored if an SSL connection is not made.
|
||||
* Specify the file containing the SSL certificate revocation list, replacing the default *~/.postgresql/root.crl*. This
|
||||
parameter is ignored if an SSL connection is not made.
|
||||
* If set to True, data sent over SSL connections will be compressed else compression will be disabled, The default
|
||||
is *False*. This parameter is ignored if an SSL connection is not made, see Section 32.1.2 of the Postgres documentation
|
||||
for more details:
|
||||
|
||||
In Server mode, files can be uploaded to the server using the File chooser dialog.
|
||||
|
||||
https://www.postgresql.org/docs/9.6/static/libpq-connect.html
|
||||
|
||||
*WARNING:* In Server mode, certificates, private keys and the revocation list are stored in the per-user file storage
|
||||
area on the server, which is owned by the user account under which the pgAdmin server process is run. This means that
|
||||
administrators of the server may be able to access those files, so appropriate caution should be taken before choosing
|
||||
to use this feature.
|
||||
|
||||
* Click the *Save* button to save work.
|
||||
* 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
|
||||
* The DB restriction field allows you to enter an 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.
|
||||
* 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
|
||||
* The DB restriction field allows you to enter an 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.
|
||||
* Specify the password file which allow user to login without providing password, see Section 31 of the Postgres documentation:
|
||||
|
||||
https://www.postgresql.org/docs/9.6/static/libpq-pgpass.html
|
||||
|
||||
* Click the *Save* button to save work.
|
||||
* Click the *Cancel* button to exit without saving work.
|
||||
* Click the *Reset* button to restore configuration parameters.
|
||||
|
46
web/migrations/versions/ef590e979b0d_.py
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
"""empty message
|
||||
|
||||
Revision ID: ef590e979b0d
|
||||
Revises: d85a62333272
|
||||
Create Date: 2017-08-23 18:37:14.836988
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from pgadmin.model import db
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ef590e979b0d'
|
||||
down_revision = 'd85a62333272'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN passfile TEXT'
|
||||
)
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN sslcert TEXT'
|
||||
)
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN sslkey TEXT'
|
||||
)
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN sslrootcert TEXT'
|
||||
)
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN sslcrl TEXT'
|
||||
)
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN sslcompression '
|
||||
'INTEGER default 0'
|
||||
)
|
||||
db.engine.execute(
|
||||
'UPDATE server SET sslcompression=0'
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
@ -25,7 +25,7 @@ import config
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.model import db, Server, ServerGroup, User
|
||||
from pgadmin.utils.driver import get_driver
|
||||
|
||||
from pgadmin.utils import get_storage_directory
|
||||
|
||||
def has_any(data, keys):
|
||||
"""
|
||||
@ -230,7 +230,51 @@ class ServerNode(PGChildNodeView):
|
||||
'((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$'
|
||||
pat4 = re.compile(EXP_IP4)
|
||||
pat6 = re.compile(EXP_IP6)
|
||||
SSL_MODES = ['prefer','require', 'verify-ca', 'verify-full']
|
||||
|
||||
def check_ssl_fields(self, data):
|
||||
"""
|
||||
This function will allow us to check and set defaults for
|
||||
SSL fields
|
||||
|
||||
Args:
|
||||
data: Response data
|
||||
|
||||
Returns:
|
||||
Flag and Data
|
||||
"""
|
||||
flag = False
|
||||
|
||||
if 'sslmode' in data and data['sslmode'] in self.SSL_MODES:
|
||||
flag = True
|
||||
ssl_fields = [
|
||||
'sslcert', 'sslkey', 'sslrootcert', 'sslcrl', 'sslcompression'
|
||||
]
|
||||
# Required SSL fields for SERVER mode from user
|
||||
required_ssl_fields_server_mode = ['sslcert', 'sslkey']
|
||||
|
||||
for field in ssl_fields:
|
||||
if field not in data:
|
||||
# In Server mode,
|
||||
# we will set dummy SSL certificate file path which will
|
||||
# prevent using default SSL certificates from web servers
|
||||
|
||||
if config.SERVER_MODE and \
|
||||
field in required_ssl_fields_server_mode:
|
||||
# Set file manager directory from preference
|
||||
import os
|
||||
storage_dir = get_storage_directory()
|
||||
file_extn = '.key' if field.endswith('key') else '.crt'
|
||||
dummy_ssl_file = os.path.join(
|
||||
storage_dir, '.postgresql',
|
||||
'postgresql' + file_extn
|
||||
)
|
||||
data[field] = dummy_ssl_file
|
||||
# For Desktop mode, we will allow to default
|
||||
else:
|
||||
data[field] = None
|
||||
|
||||
return flag, data
|
||||
|
||||
def nodes(self, gid):
|
||||
res = []
|
||||
@ -381,7 +425,13 @@ class ServerNode(PGChildNodeView):
|
||||
'gid': 'servergroup_id',
|
||||
'comment': 'comment',
|
||||
'role': 'role',
|
||||
'db_res': 'db_res'
|
||||
'db_res': 'db_res',
|
||||
'passfile': 'passfile',
|
||||
'sslcert': 'sslcert',
|
||||
'sslkey': 'sslkey',
|
||||
'sslrootcert': 'sslrootcert',
|
||||
'sslcrl': 'sslcrl',
|
||||
'sslcompression': 'sslcompression'
|
||||
}
|
||||
|
||||
disp_lbl = {
|
||||
@ -411,8 +461,6 @@ class ServerNode(PGChildNodeView):
|
||||
errormsg=gettext('Host address not valid')
|
||||
)
|
||||
|
||||
|
||||
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
@ -430,7 +478,12 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
for arg in config_param_map:
|
||||
if arg in data:
|
||||
setattr(server, config_param_map[arg], data[arg])
|
||||
value = data[arg]
|
||||
# sqlite3 do not have boolean type so we need to convert
|
||||
# it manually to integer
|
||||
if arg == 'sslcompression':
|
||||
value = 1 if value else 0
|
||||
setattr(server, config_param_map[arg], value)
|
||||
idx += 1
|
||||
|
||||
if idx == 0:
|
||||
@ -532,6 +585,8 @@ class ServerNode(PGChildNodeView):
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
|
||||
is_ssl = True if server.ssl_mode in self.SSL_MODES else False
|
||||
|
||||
return ajax_response(
|
||||
response={
|
||||
'id': server.id,
|
||||
@ -549,7 +604,14 @@ class ServerNode(PGChildNodeView):
|
||||
'version': manager.ver,
|
||||
'sslmode': server.ssl_mode,
|
||||
'server_type': manager.server_type if connected else 'pg',
|
||||
'db_res': server.db_res.split(',') if server.db_res else None
|
||||
'db_res': server.db_res.split(',') if server.db_res else None,
|
||||
'passfile': server.passfile if server.passfile else None,
|
||||
'sslcert': server.sslcert if is_ssl else None,
|
||||
'sslkey': server.sslkey if is_ssl else None,
|
||||
'sslrootcert': server.sslrootcert if is_ssl else None,
|
||||
'sslcrl': server.sslcrl if is_ssl else None,
|
||||
'sslcompression': True if is_ssl and server.sslcompression
|
||||
else False
|
||||
}
|
||||
)
|
||||
|
||||
@ -588,6 +650,9 @@ class ServerNode(PGChildNodeView):
|
||||
errormsg=gettext('Host address not valid')
|
||||
)
|
||||
|
||||
# To check ssl configuration
|
||||
is_ssl, data = self.check_ssl_fields(data)
|
||||
|
||||
server = None
|
||||
|
||||
try:
|
||||
@ -603,7 +668,12 @@ class ServerNode(PGChildNodeView):
|
||||
ssl_mode=data[u'sslmode'],
|
||||
comment=data[u'comment'] if u'comment' in data else None,
|
||||
role=data[u'role'] if u'role' in data else None,
|
||||
db_res=','.join(data[u'db_res']) if u'db_res' in data else None
|
||||
db_res=','.join(data[u'db_res']) if u'db_res' in data else None,
|
||||
sslcert=data['sslcert'] if is_ssl else None,
|
||||
sslkey=data['sslkey'] if is_ssl else None,
|
||||
sslrootcert=data['sslrootcert'] if is_ssl else None,
|
||||
sslcrl=data['sslcrl'] if is_ssl else None,
|
||||
sslcompression=1 if is_ssl and data['sslcompression'] else 0
|
||||
)
|
||||
db.session.add(server)
|
||||
db.session.commit()
|
||||
@ -621,14 +691,21 @@ class ServerNode(PGChildNodeView):
|
||||
if 'password' in data and data["password"] != '':
|
||||
# login with password
|
||||
have_password = True
|
||||
passfile = None
|
||||
password = data['password']
|
||||
password = encrypt(password, current_user.password)
|
||||
elif 'passfile' in data and data["passfile"] != '':
|
||||
passfile = data['passfile']
|
||||
setattr(server, 'passfile', passfile)
|
||||
db.session.commit()
|
||||
else:
|
||||
# Attempt password less login
|
||||
password = None
|
||||
passfile = None
|
||||
|
||||
status, errmsg = conn.connect(
|
||||
password=password,
|
||||
passfile=passfile,
|
||||
server_types=ServerType.types()
|
||||
)
|
||||
if hasattr(str, 'decode') and errmsg is not None:
|
||||
@ -774,6 +851,7 @@ class ServerNode(PGChildNodeView):
|
||||
) if request.data else {}
|
||||
|
||||
password = None
|
||||
passfile = None
|
||||
save_password = False
|
||||
|
||||
# Connect the Server
|
||||
@ -782,7 +860,8 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
if 'password' not in data:
|
||||
conn_passwd = getattr(conn, 'password', None)
|
||||
if conn_passwd is None and server.password is None:
|
||||
if conn_passwd is None and server.password is None and \
|
||||
server.passfile is None:
|
||||
# Return the password template in case password is not
|
||||
# provided, or password has not been saved earlier.
|
||||
return make_json_response(
|
||||
@ -795,6 +874,8 @@ class ServerNode(PGChildNodeView):
|
||||
_=gettext
|
||||
)
|
||||
)
|
||||
elif server.passfile and server.passfile != '':
|
||||
passfile = server.passfile
|
||||
else:
|
||||
password = conn_passwd or server.password
|
||||
else:
|
||||
@ -815,6 +896,7 @@ class ServerNode(PGChildNodeView):
|
||||
try:
|
||||
status, errmsg = conn.connect(
|
||||
password=password,
|
||||
passfile=passfile,
|
||||
server_types=ServerType.types()
|
||||
)
|
||||
except Exception as e:
|
||||
|
@ -10,6 +10,7 @@ define('pgadmin.node.server', [
|
||||
) {
|
||||
|
||||
if (!pgBrowser.Nodes['server']) {
|
||||
var SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full'];
|
||||
|
||||
var SecurityModel = pgBrowser.SecLabelModel = pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
@ -611,7 +612,13 @@ define('pgadmin.node.server', [
|
||||
connect_now: true,
|
||||
password: undefined,
|
||||
save_password: false,
|
||||
db_res: ''
|
||||
db_res: '',
|
||||
passfile: undefined,
|
||||
sslcompression: false,
|
||||
sslcert: undefined,
|
||||
sslkey: undefined,
|
||||
sslrootcert: undefined,
|
||||
sslcrl: undefined
|
||||
},
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
@ -653,13 +660,6 @@ 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: 'db_res', label: gettext('DB restriction'), type: 'select2', group: gettext('Advanced'),
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', select2: {multiple: true, allowClear: false,
|
||||
tags: true, tokenSeparators: [','], first_empty: false, selectOnClose: true, emptyOptions: true}
|
||||
},{
|
||||
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', min: 1024, max: 65535
|
||||
@ -688,7 +688,7 @@ define('pgadmin.node.server', [
|
||||
id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
||||
},{
|
||||
id: 'sslmode', label: gettext('SSL mode'), type: 'options', group: gettext('Connection'),
|
||||
id: 'sslmode', label: gettext('SSL mode'), type: 'options', group: gettext('SSL'),
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
|
||||
'options': [
|
||||
{label: 'Allow', value: 'allow'},
|
||||
@ -698,6 +698,96 @@ define('pgadmin.node.server', [
|
||||
{label: 'Verify-CA', value: 'verify-ca'},
|
||||
{label: 'Verify-Full', value: 'verify-full'}
|
||||
]
|
||||
},{
|
||||
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode']
|
||||
},{
|
||||
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode']
|
||||
},{
|
||||
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode']
|
||||
},{
|
||||
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode']
|
||||
},{
|
||||
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
|
||||
mode: ['edit', 'create'], group: gettext('SSL'),
|
||||
'options': { 'onText': 'True', 'offText': 'False',
|
||||
'onColor': 'success', 'offColor': 'danger', 'size': 'small'},
|
||||
deps: ['sslmode'], disabled: 'isSSL'
|
||||
},{
|
||||
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(m) {
|
||||
var sslcert = m.get('sslcert');
|
||||
return !_.isUndefined(sslcert) && !_.isNull(sslcert);
|
||||
}
|
||||
},{
|
||||
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(m) {
|
||||
var sslkey = m.get('sslkey');
|
||||
return !_.isUndefined(sslkey) && !_.isNull(sslkey);
|
||||
}
|
||||
},{
|
||||
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(m) {
|
||||
var sslrootcert = m.get('sslrootcert');
|
||||
return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
|
||||
}
|
||||
},{
|
||||
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(m) {
|
||||
var sslcrl = m.get('sslcrl');
|
||||
return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
|
||||
}
|
||||
},{
|
||||
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
|
||||
mode: ['properties'], group: gettext('SSL'),
|
||||
'options': { 'onText': 'True', 'offText': 'False',
|
||||
'onColor': 'success', 'offColor': 'danger', 'size': 'small'},
|
||||
deps: ['sslmode'], visible: function(m) {
|
||||
var sslmode = m.get('sslmode');
|
||||
return _.indexOf(SSL_MODES, sslmode) != -1;
|
||||
}
|
||||
},{
|
||||
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
|
||||
},{
|
||||
id: 'db_res', label: gettext('DB restriction'), type: 'select2', group: gettext('Advanced'),
|
||||
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', select2: {multiple: true, allowClear: false,
|
||||
tags: true, tokenSeparators: [','], first_empty: false, selectOnClose: true, emptyOptions: true}
|
||||
},{
|
||||
id: 'passfile', label: gettext('Password File'), type: 'text',
|
||||
group: gettext('Advanced'), mode: ['edit', 'create'],
|
||||
disabled: 'isConnected', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*']
|
||||
},{
|
||||
id: 'passfile', label: gettext('Password File'), type: 'text',
|
||||
group: gettext('Advanced'), mode: ['properties'],
|
||||
visible: function(m) {
|
||||
var passfile = m.get('passfile');
|
||||
return !_.isUndefined(passfile) && !_.isNull(passfile);
|
||||
}
|
||||
}],
|
||||
validate: function() {
|
||||
var err = {},
|
||||
@ -790,6 +880,14 @@ define('pgadmin.node.server', [
|
||||
},
|
||||
isConnected: function(model) {
|
||||
return model.get('connected');
|
||||
},
|
||||
isSSL: function(model) {
|
||||
var ssl_mode = model.get('sslmode');
|
||||
// If server is not connected and have required SSL option
|
||||
if(model.get('connected')) {
|
||||
return true;
|
||||
}
|
||||
return _.indexOf(SSL_MODES, ssl_mode) == -1;
|
||||
}
|
||||
}),
|
||||
connection_lost: function(i, resp) {
|
||||
|
@ -156,7 +156,8 @@ class FileManagerModule(PgAdminModule):
|
||||
'file_manager.get_trans_id',
|
||||
'file_manager.delete_trans_id',
|
||||
'file_manager.save_last_dir',
|
||||
'file_manager.save_file_dialog_view'
|
||||
'file_manager.save_file_dialog_view',
|
||||
'file_manager.save_show_hidden_file_option'
|
||||
]
|
||||
|
||||
def get_file_size_preference(self):
|
||||
@ -181,6 +182,11 @@ class FileManagerModule(PgAdminModule):
|
||||
options=[{'label': gettext('List'), 'value': 'list'},
|
||||
{'label': gettext('Grid'), 'value': 'grid'}]
|
||||
)
|
||||
self.show_hidden_files = self.preference.register(
|
||||
'options', 'show_hidden_files',
|
||||
gettext("Show hidden files and folders?"), 'boolean', False,
|
||||
category_label=gettext('Options')
|
||||
)
|
||||
|
||||
|
||||
# Initialise the module
|
||||
@ -243,13 +249,18 @@ def file_manager_config(trans_id):
|
||||
data = Filemanager.get_trasaction_selection(trans_id)
|
||||
pref = Preferences.module('file_manager')
|
||||
file_dialog_view = pref.preference('file_dialog_view').get()
|
||||
show_hidden_files = pref.preference('show_hidden_files').get()
|
||||
|
||||
return Response(response=render_template(
|
||||
"file_manager/js/file_manager_config.json", _=gettext,
|
||||
data=data,
|
||||
file_dialog_view=file_dialog_view),
|
||||
"file_manager/js/file_manager_config.json",
|
||||
_=gettext,
|
||||
data=data,
|
||||
file_dialog_view=file_dialog_view,
|
||||
show_hidden_files=show_hidden_files
|
||||
),
|
||||
status=200,
|
||||
mimetype="application/json")
|
||||
mimetype="application/json"
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
@ -300,6 +311,17 @@ def save_file_dialog_view(trans_id):
|
||||
data={'status': True}
|
||||
)
|
||||
|
||||
@blueprint.route(
|
||||
"/save_show_hidden_file_option/<int:trans_id>", methods=["PUT"],
|
||||
endpoint='save_show_hidden_file_option'
|
||||
)
|
||||
@login_required
|
||||
def save_show_hidden_file_option(trans_id):
|
||||
blueprint.show_hidden_files.set(req.json['show_hidden'])
|
||||
return make_json_response(
|
||||
data={'status': True}
|
||||
)
|
||||
|
||||
|
||||
class Filemanager(object):
|
||||
"""FileManager Class."""
|
||||
@ -521,12 +543,14 @@ class Filemanager(object):
|
||||
kernel32.SetThreadErrorMode(oldmode, ctypes.byref(oldmode))
|
||||
|
||||
@staticmethod
|
||||
def list_filesystem(dir, path, trans_data, file_type):
|
||||
def list_filesystem(dir, path, trans_data, file_type, show_hidden):
|
||||
"""
|
||||
It lists all file and folders within the given
|
||||
directory.
|
||||
"""
|
||||
Filemanager.suspend_windows_warning()
|
||||
is_show_hidden_files = show_hidden
|
||||
|
||||
path = unquote(path)
|
||||
if hasattr(str, 'decode'):
|
||||
path = unquote(path).encode('utf-8').decode('utf-8')
|
||||
@ -595,8 +619,9 @@ class Filemanager(object):
|
||||
protected = 0
|
||||
system_path = os.path.join(os.path.join(orig_path, f))
|
||||
|
||||
# continue if file/folder is hidden
|
||||
if is_folder_hidden(system_path) or f.startswith('.'):
|
||||
# continue if file/folder is hidden (based on user preference)
|
||||
if not is_show_hidden_files and \
|
||||
(is_folder_hidden(system_path) or f.startswith('.')):
|
||||
continue
|
||||
|
||||
user_path = os.path.join(os.path.join(user_dir, f))
|
||||
@ -784,7 +809,8 @@ class Filemanager(object):
|
||||
|
||||
return thefile
|
||||
|
||||
def getfolder(self, path=None, file_type="", name=None, req=None):
|
||||
def getfolder(self, path=None, file_type="", name=None, req=None,
|
||||
show_hidden=False):
|
||||
"""
|
||||
Returns files and folders in give path
|
||||
"""
|
||||
@ -795,7 +821,7 @@ class Filemanager(object):
|
||||
if not dir.endswith('/'):
|
||||
dir += u'/'
|
||||
|
||||
filelist = self.list_filesystem(dir, path, trans_data, file_type)
|
||||
filelist = self.list_filesystem(dir, path, trans_data, file_type, show_hidden)
|
||||
return filelist
|
||||
|
||||
def rename(self, old=None, new=None, req=None):
|
||||
|
@ -555,7 +555,6 @@ button.list span {
|
||||
|
||||
.allowed_file_types .change_file_types label {
|
||||
float: right;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.upload_file .file_upload_main {
|
||||
@ -726,3 +725,7 @@ a.dz-remove {
|
||||
height: 100%;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
div.change_file_types span {
|
||||
padding-left:10px;
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ var getFileName = function(name) {
|
||||
var loadData = function(url) {
|
||||
return $.ajax({
|
||||
async: false,
|
||||
cache: false,
|
||||
url: url,
|
||||
dataType: 'jsonp',
|
||||
contentType: "application/json; charset=utf-8"
|
||||
@ -70,6 +71,16 @@ var save_file_dialog_view = function(view, trans_id) {
|
||||
});
|
||||
};
|
||||
|
||||
var save_show_hidden_file_option = function(option, trans_id) {
|
||||
return $.ajax({
|
||||
url: url_for('file_manager.save_show_hidden_file_option', {'trans_id': trans_id}),
|
||||
type: 'PUT',
|
||||
async: true,
|
||||
data: JSON.stringify({'show_hidden': option}),
|
||||
contentType: 'application/json'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* preg_replace
|
||||
@ -523,7 +534,8 @@ var getFolderInfo = function(path, file_type) {
|
||||
var post_data = {
|
||||
'path': path,
|
||||
'mode': 'getfolder',
|
||||
'file_type': file_type || "*"
|
||||
'file_type': file_type || "*",
|
||||
'show_hidden': $('#show_hidden').prop('checked')
|
||||
};
|
||||
|
||||
var lg = pgAdmin.FileUtils.lg;
|
||||
@ -1110,6 +1122,9 @@ pgAdmin.FileUtils = {
|
||||
have_all_types = false;
|
||||
|
||||
var select_box = "<div class='change_file_types'>" +
|
||||
gettext("Show hidden files and folders") +
|
||||
"? <input type='checkbox' id='show_hidden' onclick='pgAdmin.FileUtils.handleClick(this)'>" +
|
||||
"<span></span>" +
|
||||
"<select name='type'>";
|
||||
|
||||
while(i < types_len) {
|
||||
@ -1139,6 +1154,31 @@ pgAdmin.FileUtils = {
|
||||
curr_path = $('.currentpath').val();
|
||||
getFolderInfo(curr_path, selected_val);
|
||||
});
|
||||
|
||||
// If user have preference to show hidden files
|
||||
if (config.options.show_hidden_files) {
|
||||
setTimeout(function() {
|
||||
$("#show_hidden").click();
|
||||
}, 10);
|
||||
}
|
||||
// handle show hidden files functionality
|
||||
this.handleClick = function(cb) {
|
||||
var data = {
|
||||
'is_checked': false
|
||||
};
|
||||
|
||||
if(cb.checked) {
|
||||
$("div.allowed_file_types select").val("*").trigger("change");
|
||||
data['is_checked'] = true;
|
||||
} else {
|
||||
// User wants to hide it again
|
||||
$("div.allowed_file_types select").prop('selectedIndex', 0).trigger("change");
|
||||
data['is_checked'] = false;
|
||||
}
|
||||
// Save it in preference
|
||||
save_show_hidden_file_option(data['is_checked'], pgAdmin.FileUtils.transId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------
|
||||
|
@ -6,6 +6,7 @@
|
||||
"autoload": true,
|
||||
"showFullPath": false,
|
||||
"dialog_type": "{{data.dialog_type}}",
|
||||
"show_hidden_files": {% if show_hidden_files %}true{% else %}false{% endif%},
|
||||
"fileRoot": "{{data.fileroot}}",
|
||||
"capabilities": [{% for i in data.capabilities %}{% if loop.index != 1 %}, {% endif %}"{{i}}"{% endfor %}],
|
||||
"allowed_file_types": [{% for i in data.supported_types %}{% if loop.index != 1 %}, {% endif %}"{{i}}"{% endfor %}],
|
||||
|
@ -14,8 +14,8 @@ things:
|
||||
|
||||
1) Increment SCHEMA_VERSION below
|
||||
|
||||
2) Modify setup.py to ensure that the appropriate changes are made to the
|
||||
config database to upgrade it to the new version.
|
||||
2) Create an Alembic migratio to ensure that the appropriate changes are
|
||||
made to the config database to upgrade it to the new version.
|
||||
"""
|
||||
|
||||
from flask_security import UserMixin, RoleMixin
|
||||
@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
SCHEMA_VERSION = 14
|
||||
SCHEMA_VERSION = 15
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
@ -129,8 +129,15 @@ class Server(db.Model):
|
||||
backref=db.backref('server', cascade="all, delete-orphan"),
|
||||
lazy='joined')
|
||||
db_res = db.Column(db.Text(), nullable=True)
|
||||
|
||||
|
||||
passfile = db.Column(db.Text(), nullable=True)
|
||||
sslcert = db.Column(db.Text(), nullable=True)
|
||||
sslkey = db.Column(db.Text(), nullable=True)
|
||||
sslrootcert = db.Column(db.Text(), nullable=True)
|
||||
sslcrl = db.Column(db.Text(), nullable=True)
|
||||
sslcompression =db.Column(db.Integer(),
|
||||
db.CheckConstraint(
|
||||
'sslcompression >= 0 AND sslcompression <= 1'
|
||||
), nullable=False)
|
||||
|
||||
|
||||
class ModulePreference(db.Model):
|
||||
@ -220,4 +227,4 @@ class Keys(db.Model):
|
||||
"""Define the keys table."""
|
||||
__tablename__ = 'keys'
|
||||
name = db.Column(db.String(), nullable=False, primary_key=True)
|
||||
value = db.Column(db.String(), nullable=False)
|
||||
value = db.Column(db.String(), nullable=False)
|
||||
|
@ -330,9 +330,11 @@ class Connection(BaseConnection):
|
||||
|
||||
pg_conn = None
|
||||
password = None
|
||||
passfile = None
|
||||
mgr = self.manager
|
||||
|
||||
encpass = kwargs['password'] if 'password' in kwargs else None
|
||||
passfile = kwargs['passfile'] if 'passfile' in kwargs else None
|
||||
|
||||
if encpass is None:
|
||||
encpass = self.password or getattr(mgr, 'password', None)
|
||||
@ -385,7 +387,13 @@ class Connection(BaseConnection):
|
||||
user=user,
|
||||
password=password,
|
||||
async=self.async,
|
||||
sslmode=mgr.ssl_mode
|
||||
passfile=passfile,
|
||||
sslmode=mgr.ssl_mode,
|
||||
sslcert=mgr.sslcert,
|
||||
sslkey=mgr.sslkey,
|
||||
sslrootcert=mgr.sslrootcert,
|
||||
sslcrl=mgr.sslcrl,
|
||||
sslcompression=True if mgr.sslcompression else False
|
||||
)
|
||||
|
||||
# If connection is asynchronous then we will have to wait
|
||||
@ -1227,7 +1235,14 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
port=mgr.port,
|
||||
database=self.db,
|
||||
user=mgr.user,
|
||||
password=password
|
||||
password=password,
|
||||
passfile=mgr.passfile,
|
||||
sslmode=mgr.ssl_mode,
|
||||
sslcert=mgr.sslcert,
|
||||
sslkey=mgr.sslkey,
|
||||
sslrootcert=mgr.sslrootcert,
|
||||
sslcrl=mgr.sslcrl,
|
||||
sslcompression=True if mgr.sslcompression else False
|
||||
)
|
||||
|
||||
except psycopg2.Error as e:
|
||||
@ -1496,7 +1511,15 @@ Failed to reset the connection to the server due to following error:
|
||||
port=self.manager.port,
|
||||
database=self.db,
|
||||
user=self.manager.user,
|
||||
password=password
|
||||
password=password,
|
||||
passfile=self.manager.passfile,
|
||||
sslmode=self.manager.ssl_mode,
|
||||
sslcert=self.manager.sslcert,
|
||||
sslkey=self.manager.sslkey,
|
||||
sslrootcert=self.manager.sslrootcert,
|
||||
sslcrl=self.manager.sslcrl,
|
||||
sslcompression=True if self.manager.sslcompression
|
||||
else False
|
||||
)
|
||||
|
||||
# Get the cursor and run the query
|
||||
@ -1678,6 +1701,12 @@ class ServerManager(object):
|
||||
self.db_info = dict()
|
||||
self.server_types = None
|
||||
self.db_res = server.db_res
|
||||
self.passfile = server.passfile
|
||||
self.sslcert = server.sslcert
|
||||
self.sslkey = server.sslkey
|
||||
self.sslrootcert = server.sslrootcert
|
||||
self.sslcrl = server.sslcrl
|
||||
self.sslcompression = True if server.sslcompression else False
|
||||
|
||||
for con in self.connections:
|
||||
self.connections[con]._release()
|
||||
|