Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions web/pgadmin/utils/passexec.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ def get(self):
raise NotImplementedError('Passexec not available in server mode')
driver = get_driver(config.PG_DEFAULT_DRIVER)
self.cmd = str(self.cmd)
self.cmd = self.cmd.replace('%HOSTNAME%', self.host)
self.cmd = self.cmd.replace('%PORT%', str(self.port))
self.cmd = self.cmd.replace('%USERNAME%',
driver.qtIdent(None,self.username))
self.cmd = self.cmd.replace('%HOSTNAME%', self.host or '')
self.cmd = self.cmd.replace(
'%PORT%', str(self.port) if self.port is not None else '')
self.cmd = self.cmd.replace(
'%USERNAME%',
driver.qtIdent(None, self.username) if self.username else '')
with self.lock:
if not self.password or self.is_expired():
if not self.cmd:
Expand Down
49 changes: 49 additions & 0 deletions web/pgadmin/utils/tests/test_passexec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################

"""Regression test for PasswordExec.get() with a Service-only connection.

When a server is configured via the ``Service`` field (pg_service.conf) with
host, port and username left blank, ``PasswordExec`` is constructed with
those attributes as ``None``. ``get()`` used to pass them straight into
``str.replace()``, which raises ``TypeError: replace() argument 2 must be
str, not None``. The fix substitutes an empty string for any unset
placeholder instead."""

from unittest.mock import patch, MagicMock

from pgadmin.utils.route import BaseTestGenerator
from pgadmin.utils.passexec import PasswordExec


class TestPasswordExecServiceOnly(BaseTestGenerator):
"""PasswordExec.get() must not raise when host/port/username are None."""

def setUp(self):
pass

def runTest(self):
pexec = PasswordExec(
'echo %HOSTNAME%:%PORT%:%USERNAME%', None, None, None)

fake_driver = MagicMock()
fake_driver.qtIdent.return_value = 'quoted'
fake_proc = MagicMock(stdout='secret\n')

with self.app.app_context(), \
patch('pgadmin.utils.passexec.get_driver',
return_value=fake_driver), \
patch('pgadmin.utils.passexec.subprocess.run',
return_value=fake_proc) as mock_run, \
patch('pgadmin.utils.passexec.config.SERVER_MODE', False):
password = pexec.get()

self.assertEqual(password, 'secret')
self.assertEqual(mock_run.call_args[0][0], 'echo ::')
fake_driver.qtIdent.assert_not_called()
Loading