Fix connect-time username prompt never reaching SSH/RDP authentication

The username entered at the connect-time prompt (added for issue #21)
was updating SessionTab's own in-memory Profile copy, but
SshSessionBackend/RdpSessionBackend are constructed with -- and only
ever read from -- their own separate Profile copy on a worker thread,
which never saw that edit. Authentication was still built from the
original (blank) username regardless of what was typed into the
prompt.

SessionConnectOptions gains a username field, populated by SessionTab
on every connect attempt and threaded through the same way password
already is; both backends now prefer options.username over
profile().username. Covered by a new SSH regression test using an
exact-match fixture host that only succeeds for a specific
user@host target.

Bump version to v2026.9.16.5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 08:40:34 -06:00
co-authored by Claude Sonnet 5
parent 0cdf930303
commit b3cedcfa48
8 changed files with 93 additions and 6 deletions
+16
View File
@@ -6,6 +6,22 @@
# host, optionally as user@host, as the final argument).
for arg in "$@"; do
case "$arg" in
prompted-user@requireuser)
# Only the exact user@host below is accepted -- used to prove a
# username supplied via SessionConnectOptions (prompted for at
# connect time because the saved profile's own username was
# blank) actually reaches the ssh command line, not just that
# *some* connection to this host succeeds.
echo "Welcome to the fake host."
while IFS= read -r line; do
echo "$line"
done
exit 0
;;
*@requireuser|requireuser)
echo "Permission denied (publickey,password)." >&2
exit 255
;;
*@succeed|succeed)
echo "Welcome to the fake host."
# Stay alive echoing stdin back (simulates an interactive
+34 -2
View File
@@ -21,6 +21,13 @@ Profile makeProfile(const QString& fixtureHost)
return profile;
}
Profile makeBlankUsernameProfile(const QString& fixtureHost)
{
Profile profile = makeProfile(fixtureHost);
profile.username.clear();
return profile;
}
SessionConnectOptions makeOptions()
{
SessionConnectOptions options;
@@ -50,10 +57,12 @@ private slots:
void connectionRefusedReachesFailedState();
void sendInputEchoesThroughOutputReceived();
void reconnectRestartsAndReachesConnectedAgain();
void connectOptionsUsernameReachesProcessWhenProfileUsernameIsBlank();
private:
QString fixturePath() const;
void createBackend(const QString& fixtureHost);
void createBackend(const Profile& profile);
std::unique_ptr<SshSessionBackend> m_backend;
SessionState m_lastState = SessionState::Disconnected;
@@ -69,8 +78,12 @@ QString TestSshSessionBackend::fixturePath() const
void TestSshSessionBackend::createBackend(const QString& fixtureHost)
{
m_backend =
std::make_unique<SshSessionBackend>(makeProfile(fixtureHost), fixturePath(), nullptr);
createBackend(makeProfile(fixtureHost));
}
void TestSshSessionBackend::createBackend(const Profile& profile)
{
m_backend = std::make_unique<SshSessionBackend>(profile, fixturePath(), nullptr);
connect(m_backend.get(),
&SessionBackend::stateChanged,
this,
@@ -232,5 +245,24 @@ void TestSshSessionBackend::reconnectRestartsAndReachesConnectedAgain()
QTRY_COMPARE(m_lastState, SessionState::Connected);
}
void TestSshSessionBackend::connectOptionsUsernameReachesProcessWhenProfileUsernameIsBlank()
{
// Regression test for a bug where a username entered at the
// connect-time prompt (SessionTab::requestConnectOptions(), for a
// profile with no saved username -- issue #21) never actually reached
// the ssh process: SshSessionBackend built its target purely from
// profile().username, which is a separate copy captured when the
// backend was constructed and never sees SessionTab's later edit.
// fixtures/fake_ssh.sh's "requireuser" host only accepts the exact
// target "prompted-user@requireuser", so this fails unless
// SessionConnectOptions::username is actually used.
createBackend(makeBlankUsernameProfile(QStringLiteral("requireuser")));
SessionConnectOptions options = makeOptions();
options.username = QStringLiteral("prompted-user");
m_backend->connectSession(options);
QTRY_COMPARE(m_lastState, SessionState::Connected);
}
QTEST_GUILESS_MAIN(TestSshSessionBackend)
#include "test_ssh_session_backend.moc"