Add support to save and clear SSH Tunnel password. Fixes #3511

This commit is contained in:
Akshay Joshi
2018-08-06 15:56:46 +05:30
parent 52fc0846cd
commit c8c5f83dfe
20 changed files with 331 additions and 73 deletions

View File

@@ -10,4 +10,10 @@ Use *Clear Saved Password* functionality to clear the saved password for the dat
*Clear Saved Password* shows in the context menu for the selected server as well as under the *Object* menu on the top menu bar.
Use *Clear SSH Tunnel Password* functionality to clear the saved password of SSH Tunnel to connect to the database server.
.. image:: images/clear_tunnel_password.png
*Clear SSH Tunnel Password* shows in the context menu for the selected server as well as under the *Object* menu on the top menu bar.
**Note:** It will be enabled/visible when the password for the selected database server is already saved.

View File

@@ -14,6 +14,16 @@ Provide authentication information for the selected server:
* Use the *Password* field to provide the password of the user that is associated with the defined server.
* Check the box next to *Save Password* to instruct the server to save the password for future connections; if you save the password, you will not be prompted when reconnecting to the database server with this server definition.
In case of SSH Tunneling, *Connect to Server* dialog will prompt for SSH Tunnel and Database server passwords if not already saved.
.. image:: images/connect_to_tunneled_server.png
:alt: Connect to server dialog
Provide authentication information for the selected server:
* Use the *Password* field to provide the password of the user that is associated with the defined server.
* Check the box next to respective *Save Password* to instruct the server to save the password for future connections; if you save the password, you will not be prompted when reconnecting to the database server with this server definition.
The pgAdmin client displays a message in a green status bar in the lower right corner when the server connects successfully.
If you receive an error message while attempting a connection, verify that your network is allowing the pgAdmin host and the host of the database server to communicate. For detailed information about a specific error message, please see the :ref:`Connection Error <connect_error>` help page.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -28,38 +28,41 @@ Use the *File* menu to access the following options:
The *Object* menu is context-sensitive. Use the *Object* menu to access the following options (in alphabetical order):
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| Option | Action |
+========================+==========================================================================================================================+
| *Change Password...* | Click to open the :ref:`Change Password... <change_password_dialog>` dialog to change your password. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Clear Saved Password* | If you have saved the database server password, click to reset the saved password. |
| | Enable only when password is already saved and database server is disconnected. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Connect Server...* | Click to open the :ref:`Connect to Server <connect_to_server>` dialog to establish a connection with a server. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Create* | Click *Create* to access a context menu that provides context-sensitive selections. |
| | Your selection opens a *Create* dialog for creating a new object. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Delete/Drop* | Click to delete the currently selected object from the server. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Disconnect Server...* | Click to refresh the currently selected object. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Drop Cascade* | Click to delete the currently selected object and all dependent objects from the server. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Properties...* | Click to review or modify the currently selected object's properties. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Refresh...* | Click to refresh the currently selected object. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Scripts* | Click to open the :ref:`Query tool <query_tool>` to edit or view the selected script from the flyout menu. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Trigger(s)* | Click to *Disable* or *Enable* trigger(s) for the currently selected table. Options are displayed on the flyout menu. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Truncate* | Click to remove all rows from a table (*Truncate*) or to remove all rows from a table and its child tables |
| | (*Truncate Cascade*). Options are displayed on the flyout menu. |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *View Data* | Click to access a context menu that provides several options for viewing data (see below). |
+------------------------+--------------------------------------------------------------------------------------------------------------------------+
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| Option | Action |
+=============================+==========================================================================================================================+
| *Change Password...* | Click to open the :ref:`Change Password... <change_password_dialog>` dialog to change your password. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Clear Saved Password* | If you have saved the database server password, click to clear the saved password. |
| | Enable only when password is already saved. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Clear SSH Tunnel Password* | If you have saved the ssh tunnel password, click to clear the saved password. |
| | Enable only when password is already saved. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Connect Server...* | Click to open the :ref:`Connect to Server <connect_to_server>` dialog to establish a connection with a server. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Create* | Click *Create* to access a context menu that provides context-sensitive selections. |
| | Your selection opens a *Create* dialog for creating a new object. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Delete/Drop* | Click to delete the currently selected object from the server. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Disconnect Server...* | Click to refresh the currently selected object. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Drop Cascade* | Click to delete the currently selected object and all dependent objects from the server. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Properties...* | Click to review or modify the currently selected object's properties. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Refresh...* | Click to refresh the currently selected object. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Scripts* | Click to open the :ref:`Query tool <query_tool>` to edit or view the selected script from the flyout menu. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Trigger(s)* | Click to *Disable* or *Enable* trigger(s) for the currently selected table. Options are displayed on the flyout menu. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Truncate* | Click to remove all rows from a table (*Truncate*) or to remove all rows from a table and its child tables |
| | (*Truncate Cascade*). Options are displayed on the flyout menu. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *View Data* | Click to access a context menu that provides several options for viewing data (see below). |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
**The Tools Menu**

View File

@@ -36,5 +36,6 @@ Bug fixes
| `Bug #3458 <https://redmine.postgresql.org/issues/3458>`_ - pgAdmin4 should work with python 3.7.
| `Bug #3468 <https://redmine.postgresql.org/issues/3468>`_ - Support SSH tunneling with keys that don't have a passphrase.
| `Bug #3471 <https://redmine.postgresql.org/issues/3471>`_ - Ensure the SSH tunnel port number is honoured.
| `Bug #3511 <https://redmine.postgresql.org/issues/3511>`_ - Add support to save and clear SSH Tunnel password.
| `Bug #3526 <https://redmine.postgresql.org/issues/3526>`_ - COST statement should not be automatically duplicated after creating trigger function.
| `Bug #3527 <https://redmine.postgresql.org/issues/3527>`_ - View Data->Filtered Rows dialog should be displayed.

View File

@@ -30,7 +30,7 @@ Use the fields in the *Connection* tab to configure a connection:
* 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 *Username* 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.
* Check the box next to *Save password?* to instruct pgAdmin to save the password for future use. Use :ref:`Clear Saved Password <clear_saved_passwords>` to remove the saved password.
* 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 *Service* field to specify the service name. For more information, see `Section 33.16 of the Postgres documentation <https://www.postgresql.org/docs/10/static/libpq-pgservice.html>`_.
@@ -73,7 +73,9 @@ not be able to connect directly.
* Select the *Password* option to specify that pgAdmin will use a password for authentication to the SSH host. This is the default.
* Select the *Identity file* to specify that pgAdmin will use a private key file when connecting.
* If the SSH host is expecting a private key file for authentication, use the *Identity file* field to specify the location of the key file.
* If the SSH host is expecting a password, use the *Password/Passphrase* field to specify the password, or if an identity file is being used, the passphrase.
* If the SSH host is expecting a password of the user name or an identity file if being used, use the *Password* field to specify the password.
* Check the box next to *Save password?* to instruct pgAdmin to save the password for future use. Use :ref:`Clear SSH Tunnel Password <clear_saved_passwords>` to remove the saved password.
Click the *Advanced* tab to continue.

View File

@@ -390,6 +390,9 @@ SESSION_SKIP_PATHS = [
# SSH Tunneling supports only for Python 2.7 and 3.4+
##########################################################################
SUPPORT_SSH_TUNNEL = True
# Allow SSH Tunnel passwords to be saved if the user chooses.
# Set to False to disable password saving.
ALLOW_SAVE_TUNNEL_PASSWORD = False
##########################################################################
# Local config settings
@@ -413,3 +416,4 @@ if (SUPPORT_SSH_TUNNEL is True and
((sys.version_info[0] == 2 and sys.version_info[1] < 7) or
(sys.version_info[0] == 3 and sys.version_info[1] < 4))):
SUPPORT_SSH_TUNNEL = False
ALLOW_SAVE_TUNNEL_PASSWORD = False

View File

@@ -0,0 +1,32 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Added new column 'tunnel_password' to save the password of SSH Tunnel.
Revision ID: aa86fb60b73d
Revises: 493cd3e39c0c
Create Date: 2018-07-26 11:19:50.879849
"""
from pgadmin.model import db
# revision identifiers, used by Alembic.
revision = 'aa86fb60b73d'
down_revision = '493cd3e39c0c'
branch_labels = None
depends_on = None
def upgrade():
db.engine.execute(
'ALTER TABLE server ADD COLUMN tunnel_password TEXT(64)'
)
def downgrade():
pass

View File

@@ -139,7 +139,9 @@ class ServerModule(sg.ServerGroupPluginModule):
in_recovery=in_recovery,
wal_pause=wal_paused,
is_password_saved=True if server.password is not None
else False
else False,
is_tunnel_password_saved=True
if server.tunnel_password is not None else False
)
@property
@@ -251,7 +253,8 @@ class ServerNode(PGChildNodeView):
'delete': 'pause_wal_replay', 'put': 'resume_wal_replay'
}],
'check_pgpass': [{'get': 'check_pgpass'}],
'clear_saved_password': [{'put': 'clear_saved_password'}]
'clear_saved_password': [{'put': 'clear_saved_password'}],
'clear_sshtunnel_password': [{'put': 'clear_sshtunnel_password'}]
})
EXP_IP4 = "^\s*((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." \
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." \
@@ -362,7 +365,9 @@ class ServerNode(PGChildNodeView):
in_recovery=in_recovery,
wal_pause=wal_paused,
is_password_saved=True if server.password is not None
else False
else False,
is_tunnel_password_saved=True
if server.tunnel_password is not None else False
)
)
@@ -417,7 +422,9 @@ class ServerNode(PGChildNodeView):
in_recovery=in_recovery,
wal_pause=wal_paused,
is_password_saved=True if server.password is not None
else False
else False,
is_tunnel_password_saved=True
if server.tunnel_password is not None else False
)
)
@@ -787,6 +794,7 @@ class ServerNode(PGChildNodeView):
conn = manager.connection()
have_password = False
have_tunnel_password = False
password = None
passfile = None
tunnel_password = ''
@@ -801,6 +809,7 @@ class ServerNode(PGChildNodeView):
db.session.commit()
if 'tunnel_password' in data and data["tunnel_password"] != '':
have_tunnel_password = True
tunnel_password = data['tunnel_password']
tunnel_password = \
encrypt(tunnel_password, current_user.password)
@@ -828,6 +837,13 @@ class ServerNode(PGChildNodeView):
setattr(server, 'password', password)
db.session.commit()
if 'save_tunnel_password' in data and \
data['save_tunnel_password'] and \
have_tunnel_password and \
config.ALLOW_SAVE_TUNNEL_PASSWORD:
setattr(server, 'tunnel_password', tunnel_password)
db.session.commit()
user = manager.user_info
connected = True
@@ -969,6 +985,9 @@ class ServerNode(PGChildNodeView):
passfile = None
tunnel_password = None
save_password = False
save_tunnel_password = False
prompt_password = False
prompt_tunnel_password = False
# Connect the Server
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
@@ -977,10 +996,16 @@ class ServerNode(PGChildNodeView):
# If server using SSH Tunnel
if server.use_ssh_tunnel:
if 'tunnel_password' not in data:
return self.get_response_for_password(server, 428)
if server.tunnel_password is None:
prompt_tunnel_password = True
else:
tunnel_password = server.tunnel_password
else:
tunnel_password = data['tunnel_password'] if 'tunnel_password'\
in data else ''
tunnel_password = data['tunnel_password'] \
if 'tunnel_password'in data else ''
save_tunnel_password = data['save_tunnel_password'] \
if tunnel_password and 'save_tunnel_password' in data \
else False
# Encrypt the password before saving with user's login
# password key.
try:
@@ -995,9 +1020,7 @@ class ServerNode(PGChildNodeView):
conn_passwd = getattr(conn, 'password', None)
if conn_passwd is None and server.password is None and \
server.passfile is None and server.service is None:
# Return the password template in case password is not
# provided, or password has not been saved earlier.
return self.get_response_for_password(server, 428)
prompt_password = True
elif server.passfile and server.passfile != '':
passfile = server.passfile
else:
@@ -1016,6 +1039,13 @@ class ServerNode(PGChildNodeView):
current_app.logger.exception(e)
return internal_server_error(errormsg=e.message)
# Check do we need to prompt for the database server or ssh tunnel
# password or both. Return the password template in case password is
# not provided, or password has not been saved earlier.
if prompt_password or prompt_tunnel_password:
return self.get_response_for_password(server, 428, prompt_password,
prompt_tunnel_password)
status = True
try:
status, errmsg = conn.connect(
@@ -1027,7 +1057,7 @@ class ServerNode(PGChildNodeView):
except Exception as e:
current_app.logger.exception(e)
return self.get_response_for_password(
server, 401, getattr(e, 'message', str(e)))
server, 401, True, True, getattr(e, 'message', str(e)))
if not status:
if hasattr(str, 'decode'):
@@ -1037,8 +1067,8 @@ class ServerNode(PGChildNodeView):
"Could not connected to server(#{0}) - '{1}'.\nError: {2}"
.format(server.id, server.name, errmsg)
)
return self.get_response_for_password(server, 401, errmsg)
return self.get_response_for_password(server, 401, True,
True, errmsg)
else:
if save_password and config.ALLOW_SAVE_PASSWORD:
try:
@@ -1054,6 +1084,19 @@ class ServerNode(PGChildNodeView):
return internal_server_error(errormsg=e.message)
if save_tunnel_password and config.ALLOW_SAVE_TUNNEL_PASSWORD:
try:
# Save the encrypted tunnel password.
setattr(server, 'tunnel_password', tunnel_password)
db.session.commit()
except Exception as e:
# Release Connection
current_app.logger.exception(e)
manager.release(database=server.maintenance_db)
conn = None
return internal_server_error(errormsg=e.message)
current_app.logger.info('Connection Established for server: \
%s - %s' % (server.id, server.name))
# Update the recovery and wal pause option for the server
@@ -1072,7 +1115,11 @@ class ServerNode(PGChildNodeView):
'db': manager.db,
'user': manager.user_info,
'in_recovery': in_recovery,
'wal_pause': wal_paused
'wal_pause': wal_paused,
'is_password_saved': True if server.password is not None
else False,
'is_tunnel_password_saved': True
if server.tunnel_password is not None else False,
}
)
@@ -1418,7 +1465,8 @@ class ServerNode(PGChildNodeView):
)
return internal_server_error(errormsg=str(e))
def get_response_for_password(self, server, status, errmsg=None):
def get_response_for_password(self, server, status, prompt_password=False,
prompt_tunnel_password=False, errmsg=None):
if server.use_ssh_tunnel:
return make_json_response(
success=0,
@@ -1431,7 +1479,9 @@ class ServerNode(PGChildNodeView):
tunnel_host=server.tunnel_host,
tunnel_identity_file=server.tunnel_identity_file,
errmsg=errmsg,
_=gettext
_=gettext,
prompt_tunnel_password=prompt_tunnel_password,
prompt_password=prompt_password
)
)
else:
@@ -1478,9 +1528,45 @@ class ServerNode(PGChildNodeView):
return make_json_response(
success=1,
info=gettext("Clear saved password successfully."),
info=gettext("The saved password cleared successfully."),
data={'is_password_saved': False}
)
def clear_sshtunnel_password(self, gid, sid):
"""
This function is used to remove sshtunnel password stored into
the pgAdmin's db file.
:param gid:
:param sid:
:return:
"""
try:
server = Server.query.filter_by(
user_id=current_user.id, id=sid
).first()
if server is None:
return make_json_response(
success=0,
info=gettext("Could not find the required server.")
)
setattr(server, 'tunnel_password', None)
db.session.commit()
except Exception as e:
current_app.logger.error(
"Unable to clear ssh tunnel password."
"\nError: {0}".format(str(e))
)
return internal_server_error(errormsg=str(e))
return make_json_response(
success=1,
info=gettext("The saved password cleared successfully."),
data={'is_tunnel_password_saved': False}
)
ServerNode.register_node_view(blueprint)

View File

@@ -119,6 +119,18 @@ define('pgadmin.node.server', [
}
return false;
},
},{
name: 'clear_sshtunnel_password', node: 'server', module: this,
applies: ['object', 'context'], callback: 'clear_sshtunnel_password',
label: gettext('Clear SSH Tunnel Password'), icon: 'fa fa-eraser',
priority: 12,
enable: function(node) {
if (node && node._type === 'server' &&
node.is_tunnel_password_saved) {
return true;
}
return false;
},
}]);
_.bindAll(this, 'connection_lost');
@@ -648,6 +660,46 @@ define('pgadmin.node.server', [
return false;
},
/* Reset stored ssh tunnel password */
clear_sshtunnel_password: function(args){
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
Alertify.confirm(
gettext('Clear SSH Tunnel password'),
S(
gettext('Are you sure you want to clear the saved password of SSH Tunnel for server %s?')
).sprintf(d.label).value(),
function() {
$.ajax({
url: obj.generate_url(i, 'clear_sshtunnel_password', d, true),
method:'PUT',
})
.done(function(res) {
if (res.success == 1) {
Alertify.success(res.info);
t.itemData(i).is_tunnel_password_saved=res.data.is_tunnel_password_saved;
}
else {
Alertify.error(res.info);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
});
},
function() { return true; }
);
return false;
},
},
model: pgAdmin.Browser.Node.Model.extend({
defaults: {
@@ -679,6 +731,7 @@ define('pgadmin.node.server', [
tunnel_identity_file: undefined,
tunnel_password: undefined,
tunnel_authentication: 0,
save_tunnel_password: false,
connect_timeout: 0,
},
// Default values!
@@ -745,28 +798,13 @@ define('pgadmin.node.server', [
},{
id: 'save_password', controlLabel: gettext('Save password?'),
type: 'checkbox', group: gettext('Connection'), mode: ['create'],
deps: ['connect_now', 'use_ssh_tunnel'], visible: function(model) {
deps: ['connect_now'], visible: function(model) {
return model.get('connect_now') && model.isNew();
},
disabled: function(model) {
disabled: function() {
if (!current_user.allow_save_password)
return true;
if (model.get('use_ssh_tunnel')) {
if (model.get('save_password')) {
Alertify.alert(
gettext('Stored Password'),
gettext('Database passwords cannot be stored when using SSH tunnelling. The \'Save password\' option has been turned off.')
);
}
setTimeout(function() {
model.set('save_password', false);
}, 10);
return true;
}
return false;
},
},{
@@ -918,6 +956,19 @@ define('pgadmin.node.server', [
disabled: function(model) {
return !model.get('use_ssh_tunnel');
},
}, {
id: 'save_tunnel_password', controlLabel: gettext('Save password?'),
type: 'checkbox', group: gettext('SSH Tunnel'), mode: ['create'],
deps: ['connect_now', 'use_ssh_tunnel'], visible: function(model) {
return model.get('connect_now') && model.isNew();
},
disabled: function(model) {
if (!current_user.allow_save_tunnel_password ||
!model.get('use_ssh_tunnel'))
return true;
return false;
},
}, {
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',

View File

@@ -4,6 +4,7 @@
<div class='control-label'>{{ errmsg }}</div>
</div>
{% endif %}
{% if prompt_tunnel_password %}
{% if tunnel_identity_file %}
<div><b>{{ _('SSH Tunnel password for the identity file \'{0}\' to connect the server "{1}"').format(tunnel_identity_file, tunnel_host) }}</b></div>
{% else %}
@@ -14,15 +15,28 @@
<span style="width: 97%;display: inline-block;">
<input style="width:100%" id="tunnel_password" class="form-control" name="tunnel_password" type="password">
</span>
<span style="padding-top: 5px;display: inline-block;">
<input id="save_tunnel_password" name="save_tunnel_password" type="checkbox"
{% if not config.ALLOW_SAVE_TUNNEL_PASSWORD %}disabled{% endif %}
>&nbsp;&nbsp;Save Password
</span>
</div>
<div style="padding: 5px; height: 1px;"></div>
{% endif %}
{% if prompt_password %}
<div><b>{{ _('Database server password for the user \'{0}\' to connect the server "{1}"').format(username, server_label) }}</b></div>
<div style="padding: 5px; height: 1px;"></div>
<div style="width: 100%">
<span style="width: 97%;display: inline-block;">
<input style="width:100%" id="password" class="form-control" name="password" type="password">
</span>
<span style="padding-top: 5px;display: inline-block;">
<input id="save_password" name="save_password" type="checkbox"
{% if not config.ALLOW_SAVE_PASSWORD %}disabled{% endif %}
>&nbsp;&nbsp;Save Password
</span>
</div>
<div style="padding: 5px; height: 1px;"></div>
{% endif %}
</div>
</form>

View File

@@ -20,13 +20,30 @@ class ServersWithSSHTunnelAddTestCase(BaseTestGenerator):
(
'Add server using SSH tunnel with password', dict(
url='/browser/server/obj/',
with_password=True
with_password=True,
save_password=False,
)
),
(
'Add server using SSH tunnel with identity file', dict(
url='/browser/server/obj/',
with_password=False
with_password=False,
save_password=False,
)
),
(
'Add server using SSH tunnel with password and saved it', dict(
url='/browser/server/obj/',
with_password=True,
save_password=True,
)
),
(
'Add server using SSH tunnel with identity file and save the '
'password', dict(
url='/browser/server/obj/',
with_password=False,
save_password=True,
)
),
]
@@ -48,6 +65,9 @@ class ServersWithSSHTunnelAddTestCase(BaseTestGenerator):
self.server['tunnel_authentication'] = 1
self.server['tunnel_identity_file'] = 'pkey_rsa'
if self.save_password:
self.server['tunnel_password'] = '123456'
response = self.tester.post(
url,
data=json.dumps(self.server),

View File

@@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy
#
##########################################################################
SCHEMA_VERSION = 17
SCHEMA_VERSION = 18
##########################################################################
#
@@ -164,6 +164,7 @@ class Server(db.Model):
nullable=False
)
tunnel_identity_file = db.Column(db.String(64), nullable=True)
tunnel_password = db.Column(db.String(64), nullable=True)
class ModulePreference(db.Model):

View File

@@ -150,7 +150,9 @@ def current_user_info():
else 'postgres'
),
allow_save_password='true' if config.ALLOW_SAVE_PASSWORD
else 'false'
else 'false',
allow_save_tunnel_password='true'
if config.ALLOW_SAVE_TUNNEL_PASSWORD else 'false',
),
status=200,
mimetype="application/javascript"

View File

@@ -4,6 +4,7 @@ define('pgadmin.user_management.current_user', [], function() {
'email': '{{ email }}',
'is_admin': {{ is_admin }},
'name': '{{ name }}',
'allow_save_password': {{ allow_save_password }}
'allow_save_password': {{ allow_save_password }},
'allow_save_tunnel_password': {{ allow_save_tunnel_password }}
}
});

View File

@@ -53,6 +53,7 @@ class ServerManager(object):
self.server_type = None
self.server_cls = None
self.password = None
self.tunnel_password = None
self.sid = server.id
self.host = server.host
@@ -84,6 +85,7 @@ class ServerManager(object):
self.tunnel_username = server.tunnel_username
self.tunnel_authentication = server.tunnel_authentication
self.tunnel_identity_file = server.tunnel_identity_file
self.tunnel_password = server.tunnel_password
else:
self.use_ssh_tunnel = 0
self.tunnel_host = None
@@ -91,6 +93,7 @@ class ServerManager(object):
self.tunnel_username = None
self.tunnel_authentication = None
self.tunnel_identity_file = None
self.tunnel_password = None
for con in self.connections:
self.connections[con]._release()
@@ -119,6 +122,17 @@ class ServerManager(object):
else:
res['password'] = self.password
if self.use_ssh_tunnel:
if hasattr(self, 'tunnel_password') and self.tunnel_password:
# If running under PY2
if hasattr(self.tunnel_password, 'decode'):
res['tunnel_password'] = \
self.tunnel_password.decode('utf-8')
else:
res['tunnel_password'] = str(self.tunnel_password)
else:
res['tunnel_password'] = self.tunnel_password
connections = res['connections'] = dict()
for conn_id in self.connections:
@@ -248,6 +262,9 @@ WHERE db.oid = {0}""".format(did))
try:
if 'password' in data and data['password']:
data['password'] = data['password'].encode('utf-8')
if 'tunnel_password' in data and data['tunnel_password']:
data['tunnel_password'] = \
data['tunnel_password'].encode('utf-8')
except Exception as e:
current_app.logger.exception(e)
@@ -265,6 +282,14 @@ WHERE db.oid = {0}""".format(did))
# auto_reconnect is true.
if conn_info['wasConnected'] and conn_info['auto_reconnect']:
try:
# Check SSH Tunnel needs to be created
if self.use_ssh_tunnel == 1 and not self.tunnel_created:
status, error = self.create_ssh_tunnel(
data['tunnel_password'])
# Check SSH Tunnel is alive or not.
self.check_ssh_tunnel_alive()
conn.connect(
password=data['password'],
server_types=ServerType.types()