Added option to skip the password dialog when using an identity file. #6996

pull/9209/head
Akshay Joshi 2025-10-01 15:18:07 +05:30
parent c6af61afd3
commit 999462816b
12 changed files with 117 additions and 10 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -8,7 +8,7 @@ This release contains a number of bug fixes and new features since the release o
Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16 and 17
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
**EDB Advanced Server**: 13, 14, 15, 16 and 17
@ -20,8 +20,10 @@ Bundled PostgreSQL Utilities
New features
************
| `Issue #6385 <https://github.com/pgadmin-org/pgadmin4/issues/6385>`_ - Add support of DEPENDS/NO DEPENDS ON EXTENSION for ALTER FUNCTION.
| `Issue #6394 <https://github.com/pgadmin-org/pgadmin4/issues/6394>`_ - Added "MULTIRANGE_TYPE_NAME" option while creating a Range Type.
| `Issue #6395 <https://github.com/pgadmin-org/pgadmin4/issues/6395>`_ - Added "SUBSCRIPT" option while creating a External Type.
| `Issue #6996 <https://github.com/pgadmin-org/pgadmin4/issues/6996>`_ - Added option to skip the password dialog when using an identity file.
| `Issue #8932 <https://github.com/pgadmin-org/pgadmin4/issues/8932>`_ - Added 'failover' and 'two_phase' parameter support in CREATE/ALTER SUBSCRIPTION for PostgreSQL v17+.
Housekeeping

View File

@ -177,6 +177,9 @@ not be able to connect directly.
*Identity file* field to specify the location of the key file.
* 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 *Prompt for password?* to to have pgAdmin prompt for
a password if the identity file includes one. This setting applies only when
using an identity file, which may or may not require a 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

View File

@ -0,0 +1,42 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""
Revision ID: efbbe5d5862f
Revises: e6ed5dac37c2
Create Date: 2025-09-29 18:40:30.248908
"""
from alembic import op, context
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'efbbe5d5862f'
down_revision = 'e6ed5dac37c2'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table(
"server",
table_kwargs={'sqlite_autoincrement': True}) as batch_op:
batch_op.add_column(sa.Column('tunnel_prompt_password',
sa.Integer(), server_default='0'))
with op.batch_alter_table(
"sharedserver",
table_kwargs={'sqlite_autoincrement': True}) as batch_op:
batch_op.add_column(sa.Column('tunnel_prompt_password',
sa.Integer(), server_default='0'))
def downgrade():
# pgAdmin only upgrades, downgrade not implemented.
pass

View File

@ -167,6 +167,7 @@ class ServerModule(sg.ServerGroupPluginModule):
server.tunnel_password = sharedserver.tunnel_password
server.save_password = sharedserver.save_password
server.tunnel_identity_file = sharedserver.tunnel_identity_file
server.tunnel_prompt_password = sharedserver.tunnel_prompt_password
if hasattr(server, 'connection_params') and \
hasattr(sharedserver, 'connection_params') and \
'passfile' in server.connection_params and \
@ -413,6 +414,7 @@ class ServerModule(sg.ServerGroupPluginModule):
tunnel_authentication=0,
tunnel_identity_file=None,
tunnel_keep_alive=0,
tunnel_prompt_password=0,
shared=True,
connection_params=data.connection_params,
prepare_threshold=data.prepare_threshold
@ -790,6 +792,7 @@ class ServerNode(PGChildNodeView):
'tunnel_username': 'tunnel_username',
'tunnel_authentication': 'tunnel_authentication',
'tunnel_identity_file': 'tunnel_identity_file',
'tunnel_prompt_password': 'tunnel_prompt_password',
'tunnel_keep_alive': 'tunnel_keep_alive',
'shared': 'shared',
'shared_username': 'shared_username',
@ -1086,6 +1089,8 @@ class ServerNode(PGChildNodeView):
'tunnel_username': tunnel_username,
'tunnel_identity_file': server.tunnel_identity_file
if server.tunnel_identity_file else None,
'tunnel_prompt_password': server.tunnel_prompt_password
if server.tunnel_identity_file else 0,
'tunnel_authentication': tunnel_authentication,
'tunnel_keep_alive': tunnel_keep_alive,
'kerberos_conn': bool(server.kerberos_conn),
@ -1212,6 +1217,8 @@ class ServerNode(PGChildNodeView):
tunnel_authentication=1 if data.get('tunnel_authentication',
False) else 0,
tunnel_identity_file=data.get('tunnel_identity_file', None),
tunnel_prompt_password=1 if data.get('tunnel_prompt_password',
True) else 0,
tunnel_keep_alive=data.get('tunnel_keep_alive', 0),
shared=data.get('shared', None),
shared_username=data.get('shared_username', None),
@ -1419,6 +1426,18 @@ class ServerNode(PGChildNodeView):
}
)
def is_prompt_tunnel_password(self, server):
"""
This function will check whether to prompt tunnel password or not.
"""
prompt_tunnel_password = True
# In case of identity file check the value of tunnel_prompt_password.
if server.tunnel_identity_file is not None and \
server.tunnel_prompt_password != 1:
prompt_tunnel_password = False
return prompt_tunnel_password
def connect(self, gid, sid, is_qt=False, server=None):
"""
Connect the Server and return the connection object.
@ -1502,11 +1521,12 @@ class ServerNode(PGChildNodeView):
# If server using SSH Tunnel
if server.use_ssh_tunnel:
if 'tunnel_password' not in data:
if server.tunnel_password is None:
prompt_tunnel_password = True
else:
tunnel_password = server.tunnel_password
if 'tunnel_password' not in data and \
server.tunnel_password is None:
prompt_tunnel_password = self.is_prompt_tunnel_password(server)
elif 'tunnel_password' not in data and \
server.tunnel_password is not None:
tunnel_password = server.tunnel_password
else:
tunnel_password = data['tunnel_password'] \
if 'tunnel_password' in data else ''
@ -1562,6 +1582,10 @@ class ServerNode(PGChildNodeView):
return self.get_response_for_password(
server, 428, prompt_password, prompt_tunnel_password)
# Check whether to prompt for the tunnel password in case if
# password is saved in server object or in data.
prompt_tunnel_password = self.is_prompt_tunnel_password(server)
try:
status, errmsg = conn.connect(
password=password,
@ -1571,7 +1595,7 @@ class ServerNode(PGChildNodeView):
)
except Exception as e:
return self.get_response_for_password(
server, 401, True, True,
server, 401, True, prompt_tunnel_password,
getattr(e, 'message', str(e)))
if not status:
@ -1583,7 +1607,7 @@ class ServerNode(PGChildNodeView):
return internal_server_error(errmsg)
return self.get_response_for_password(
server, 401, True, True, errmsg)
server, 401, True, prompt_tunnel_password, errmsg)
else:
if save_password and config.ALLOW_SAVE_PASSWORD:
try:

View File

@ -211,6 +211,7 @@ export default class ServerSchema extends BaseUISchema {
tunnel_port: 22,
tunnel_username: undefined,
tunnel_identity_file: undefined,
tunnel_prompt_password: false,
tunnel_password: undefined,
tunnel_authentication: false,
tunnel_keep_alive: 0,
@ -496,7 +497,22 @@ export default class ServerSchema extends BaseUISchema {
maxLength: null
},
readonly: obj.isConnected,
}, {
},
{
id: 'tunnel_prompt_password', label: gettext('Prompt for password?'),
type: 'switch', group: gettext('SSH Tunnel'), mode: ['properties', 'edit', 'create'],
deps: ['tunnel_authentication', 'use_ssh_tunnel'],
depChange: (state)=>{
if (!state.tunnel_authentication) {
return {tunnel_prompt_password: false};
}
},
disabled: function(state) {
return !state.tunnel_authentication || !state.use_ssh_tunnel;
},
helpMessage: gettext('This setting applies only when using an identity file. An identity file may or may not have a password. If set to true the system will prompt for the password.')
},
{
id: 'save_tunnel_password', label: gettext('Save password?'),
type: 'switch', group: gettext('SSH Tunnel'), mode: ['create'],
deps: ['connect_now', 'use_ssh_tunnel'],

View File

@ -633,6 +633,7 @@
"tunnel_password": "user123",
"tunnel_identity_file": "pkey_rsa",
"tunnel_keep_alive": 0,
"tunnel_prompt_password": 0,
"service": null,
"server_info": {
"id": 1,

View File

@ -77,6 +77,7 @@ class ServersSSHConnectTestCase(BaseTestGenerator):
self.service = service
self.save_password = 0
self.shared = None
self.tunnel_prompt_password = 0
mock_server_obj = TestMockServer(
self.mock_data['name'],

View File

@ -33,7 +33,7 @@ import config
#
##########################################################################
SCHEMA_VERSION = 47
SCHEMA_VERSION = 48
##########################################################################
#
@ -246,6 +246,11 @@ class Server(db.Model):
nullable=False
)
tunnel_identity_file = db.Column(db.String(64), nullable=True)
tunnel_prompt_password = db.Column(
db.Integer(), db.CheckConstraint(
'tunnel_prompt_password >= 0 AND tunnel_prompt_password <= 1'),
nullable=False
)
tunnel_password = db.Column(PgAdminDbBinaryString())
tunnel_keep_alive = db.Column(db.Integer(), nullable=True, default=0)
shared = db.Column(db.Boolean(), nullable=False)
@ -483,6 +488,11 @@ class SharedServer(db.Model):
nullable=False
)
tunnel_identity_file = db.Column(db.String(64), nullable=True)
tunnel_prompt_password = db.Column(
db.Integer(), db.CheckConstraint(
'tunnel_prompt_password >= 0 AND tunnel_prompt_password <= 1'),
nullable=False
)
tunnel_password = db.Column(PgAdminDbBinaryString())
tunnel_keep_alive = db.Column(db.Integer(), nullable=True)
shared = db.Column(db.Boolean(), nullable=False)

View File

@ -36,6 +36,7 @@
"TunnelPort": "22",
"TunnelUsername": "username",
"TunnelAuthentication": 0,
"TunnelPromptPassword": 0,
"PasswordExecCommand": "echo 'test'",
"PasswordExecExpiration": 100
}

View File

@ -525,6 +525,8 @@ def dump_database_servers(output_file, selected_servers,
server.tunnel_authentication)
add_value(attr_dict, "TunnelIdentityFile",
server.tunnel_identity_file)
add_value(attr_dict, "TunnelPromptPassword",
server.tunnel_prompt_password)
add_value(attr_dict, "TunnelKeepAlive",
server.tunnel_keep_alive)
add_value(attr_dict, "KerberosAuthentication",
@ -773,6 +775,9 @@ def load_database_servers(input_file, selected_servers,
new_server.tunnel_identity_file = \
obj.get("TunnelIdentityFile", None)
new_server.tunnel_prompt_password = \
obj.get("TunnelPromptPassword", 0)
new_server.tunnel_keep_alive = \
obj.get("TunnelKeepAlive", None)

View File

@ -99,6 +99,7 @@ class ServerManager(object):
if server.tunnel_authentication is None \
else server.tunnel_authentication
self.tunnel_identity_file = server.tunnel_identity_file
self.tunnel_prompt_password = server.tunnel_prompt_password
self.tunnel_password = server.tunnel_password
self.tunnel_keep_alive = server.tunnel_keep_alive
else:
@ -108,6 +109,7 @@ class ServerManager(object):
self.tunnel_username = None
self.tunnel_authentication = None
self.tunnel_identity_file = None
self.tunnel_prompt_password = 0
self.tunnel_password = None
self.tunnel_keep_alive = 0