Added option to skip the password dialog when using an identity file. #6996
parent
c6af61afd3
commit
999462816b
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 94 KiB |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
"TunnelPort": "22",
|
||||
"TunnelUsername": "username",
|
||||
"TunnelAuthentication": 0,
|
||||
"TunnelPromptPassword": 0,
|
||||
"PasswordExecCommand": "echo 'test'",
|
||||
"PasswordExecExpiration": 100
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue