From 6042db54c30e003eea0f36d9bc546939976cd11f Mon Sep 17 00:00:00 2001 From: Ivan Shcheklein Date: Tue, 21 Apr 2026 20:39:56 -0700 Subject: [PATCH 1/3] Fix kbdint password fallback --- dvc_ssh/client.py | 5 ++++- dvc_ssh/tests/test_client.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 dvc_ssh/tests/test_client.py diff --git a/dvc_ssh/client.py b/dvc_ssh/client.py index aacb185..32328ef 100644 --- a/dvc_ssh/client.py +++ b/dvc_ssh/client.py @@ -95,7 +95,10 @@ async def _read_private_key_interactive(self, path: "FilePath") -> "SSHKey": pass raise KeyImportError("Incorrect passphrase") - def kbdint_auth_requested(self) -> str: + def kbdint_auth_requested(self): + if self._conn._options.password is not None: + return NotImplemented + return "" async def kbdint_challenge_received( diff --git a/dvc_ssh/tests/test_client.py b/dvc_ssh/tests/test_client.py new file mode 100644 index 0000000..0bbbeab --- /dev/null +++ b/dvc_ssh/tests/test_client.py @@ -0,0 +1,21 @@ +from types import SimpleNamespace + +import pytest + +from dvc_ssh.client import InteractiveSSHClient + + +@pytest.mark.parametrize( + "password,expected", + [("secret", NotImplemented), (None, "")], +) +def test_kbdint_auth_requested(password, expected): + client = InteractiveSSHClient() + client._conn = SimpleNamespace(_options=SimpleNamespace(password=password)) + + result = client.kbdint_auth_requested() + + if expected is NotImplemented: + assert result is NotImplemented + else: + assert result == expected \ No newline at end of file From 8c0fb599649305168f1d0ee07b254d1fec4633f1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 03:41:16 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dvc_ssh/tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dvc_ssh/tests/test_client.py b/dvc_ssh/tests/test_client.py index 0bbbeab..ff06078 100644 --- a/dvc_ssh/tests/test_client.py +++ b/dvc_ssh/tests/test_client.py @@ -18,4 +18,4 @@ def test_kbdint_auth_requested(password, expected): if expected is NotImplemented: assert result is NotImplemented else: - assert result == expected \ No newline at end of file + assert result == expected From f1ecbdeddbb017215cbd31dd732d86f7c441cd58 Mon Sep 17 00:00:00 2001 From: Ivan Shcheklein Date: Tue, 21 Apr 2026 21:25:50 -0700 Subject: [PATCH 3/3] Add functional password auth test --- dvc_ssh/tests/cloud.py | 1 + dvc_ssh/tests/docker-compose.yml | 2 ++ dvc_ssh/tests/test_client.py | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/dvc_ssh/tests/cloud.py b/dvc_ssh/tests/cloud.py index de3c7a6..367a9e9 100644 --- a/dvc_ssh/tests/cloud.py +++ b/dvc_ssh/tests/cloud.py @@ -7,6 +7,7 @@ from dvc.testing.path_info import URLInfo TEST_SSH_USER = "user" +TEST_SSH_PASSWORD = "password" TEST_SSH_KEY_PATH = os.path.join( os.path.abspath(os.path.dirname(__file__)), f"{TEST_SSH_USER}.key" ) diff --git a/dvc_ssh/tests/docker-compose.yml b/dvc_ssh/tests/docker-compose.yml index ec5bcb0..ceb1895 100644 --- a/dvc_ssh/tests/docker-compose.yml +++ b/dvc_ssh/tests/docker-compose.yml @@ -4,7 +4,9 @@ services: openssh-server: image: ghcr.io/linuxserver/openssh-server environment: + - PASSWORD_ACCESS=true - USER_NAME=user + - USER_PASSWORD=password - PUBLIC_KEY_FILE=/tmp/key ports: - 2222 diff --git a/dvc_ssh/tests/test_client.py b/dvc_ssh/tests/test_client.py index ff06078..aabb9eb 100644 --- a/dvc_ssh/tests/test_client.py +++ b/dvc_ssh/tests/test_client.py @@ -2,7 +2,10 @@ import pytest +import dvc_ssh.client +from dvc_ssh import SSHFileSystem from dvc_ssh.client import InteractiveSSHClient +from dvc_ssh.tests.cloud import TEST_SSH_PASSWORD, TEST_SSH_USER @pytest.mark.parametrize( @@ -19,3 +22,22 @@ def test_kbdint_auth_requested(password, expected): assert result is NotImplemented else: assert result == expected + + +def test_password_auth_uses_configured_password(ssh_server, monkeypatch, tmp_path): + monkeypatch.setenv("HOME", str(tmp_path)) + monkeypatch.delenv("SSH_AUTH_SOCK", raising=False) + + async def fail_getpass(*args, **kwargs): + raise AssertionError("_getpass should not be called") + + monkeypatch.setattr(dvc_ssh.client, "_getpass", fail_getpass) + + fs = SSHFileSystem( + host=ssh_server["host"], + port=ssh_server["port"], + user=TEST_SSH_USER, + password=TEST_SSH_PASSWORD, + ) + + assert fs.exists("/tmp")