Add support to save and clear SSH Tunnel password. Fixes #3511
@@ -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.
|
@@ -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.
|
||||
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 113 KiB |
BIN
docs/en_US/images/clear_tunnel_password.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
docs/en_US/images/connect_to_tunneled_server.png
Normal file
After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 74 KiB |
@@ -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**
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
||||
|
||||
|
@@ -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
|
||||
|
32
web/migrations/versions/aa86fb60b73d_.py
Normal 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
|
@@ -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)
|
||||
|
@@ -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',
|
||||
|
@@ -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 %}
|
||||
> 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 %}
|
||||
> Save Password
|
||||
</span>
|
||||
</div>
|
||||
<div style="padding: 5px; height: 1px;"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
@@ -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),
|
||||
|
@@ -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):
|
||||
|
@@ -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"
|
||||
|
@@ -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 }}
|
||||
}
|
||||
});
|
||||
|
@@ -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()
|
||||
|