Allow selection of SSL certificates and pgpass files in connection properties. Fixes #2649. Fixes #2650

This commit is contained in:
Murtuza Zabuawala 2017-09-28 10:02:33 +01:00 committed by Dave Page
parent 510bc6c974
commit f855ed88ce
14 changed files with 439 additions and 58 deletions

BIN
docs/en_US/images/server_advanced.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en_US/images/server_connection.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/en_US/images/server_general.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en_US/images/server_ssl.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -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.

View 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

View File

@ -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:

View File

@ -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) {

View File

@ -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):

View File

@ -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;
}

View File

@ -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;
}
}
/*---------------------------------------------------------

View File

@ -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 %}],

View File

@ -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)

View File

@ -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()