From 8f470f309531d876f779bd50dfc94588fa5a4b69 Mon Sep 17 00:00:00 2001 From: Martin Wurzer Date: Sat, 20 Dec 2025 14:39:32 +0100 Subject: [PATCH 1/4] add preferred_auth --- dvc_ssh/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dvc_ssh/__init__.py b/dvc_ssh/__init__.py index 8053def..4ce60db 100644 --- a/dvc_ssh/__init__.py +++ b/dvc_ssh/__init__.py @@ -65,9 +65,20 @@ def _prepare_credentials(self, **config): if port := config.get("port"): login_info["port"] = port + # in case preferred_auth is empty, fallback is publickey + login_info["preferred_auth"] = [] + for option in ("password", "passphrase"): login_info[option] = config.get(option) + if login_info[option] or config.get(f"ask_{option}"): + login_info["preferred_auth"].append( + { + "password": "password", + "passphrase": "publickey", + }[option] + ) + if config.get(f"ask_{option}") and login_info[option] is None: login_info[option] = ask_password( login_info["host"], From c06b7a56a29a3193f0567d92ffc67593873c5c2c Mon Sep 17 00:00:00 2001 From: Martin Wurzer Date: Sat, 20 Dec 2025 15:32:15 +0100 Subject: [PATCH 2/4] add password based tests; --- dvc_ssh/tests/docker-compose.yml | 2 ++ dvc_ssh/tests/test_conn.py | 60 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 dvc_ssh/tests/test_conn.py diff --git a/dvc_ssh/tests/docker-compose.yml b/dvc_ssh/tests/docker-compose.yml index ec5bcb0..e7ff9e0 100644 --- a/dvc_ssh/tests/docker-compose.yml +++ b/dvc_ssh/tests/docker-compose.yml @@ -5,7 +5,9 @@ services: image: ghcr.io/linuxserver/openssh-server environment: - USER_NAME=user + - USER_PASSWORD=password - PUBLIC_KEY_FILE=/tmp/key + - PASSWORD_ACCESS=true ports: - 2222 volumes: diff --git a/dvc_ssh/tests/test_conn.py b/dvc_ssh/tests/test_conn.py new file mode 100644 index 0000000..03ab6ee --- /dev/null +++ b/dvc_ssh/tests/test_conn.py @@ -0,0 +1,60 @@ +from dvc_ssh import SSHFileSystem + +from .cloud import TEST_SSH_KEY_PATH + +# TODO: InteractiveSSHClient.public_key_auth_requested + +# "ssh": { +# "type": supported_cache_type, +# "port": Coerce(int), +# "user": str, +# "password": str, +# "ask_password": Bool, +# "passphrase": str, +# "ask_passphrase": Bool, +# "keyfile": str, +# "timeout": Coerce(int), +# "gss_auth": Bool, +# "allow_agent": Bool, +# "max_sessions": Coerce(int), +# Optional("verify", default=False): Bool, +# **REMOTE_COMMON, +# }, + + +def test_denied_user(ssh_server, mocker): + f = SSHFileSystem( + host=ssh_server["host"], + port=ssh_server["port"], + user="user", + keyfile=TEST_SSH_KEY_PATH, + ) + + assert f.fs + + +def test_password_set(ssh_server, mocker): + f = SSHFileSystem( + host=ssh_server["host"], + port=ssh_server["port"], + user="user", + password="password", + ) + + assert f.fs + + +def test_password_prompt(ssh_server, mocker): + f = SSHFileSystem( + host=ssh_server["host"], + port=ssh_server["port"], + user="user", + ask_password=True, + ) + + mock_getpass = mocker.patch( + "dvc_ssh.ask_password", + ) + mock_getpass.return_value = "password" + + assert f.fs From 7296ea1fb68f512e875c18847826410be9239f3e Mon Sep 17 00:00:00 2001 From: Martin Wurzer Date: Sat, 20 Dec 2025 15:34:21 +0100 Subject: [PATCH 3/4] clean tests; --- dvc_ssh/tests/test_conn.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/dvc_ssh/tests/test_conn.py b/dvc_ssh/tests/test_conn.py index 03ab6ee..73a7f4b 100644 --- a/dvc_ssh/tests/test_conn.py +++ b/dvc_ssh/tests/test_conn.py @@ -2,27 +2,8 @@ from .cloud import TEST_SSH_KEY_PATH -# TODO: InteractiveSSHClient.public_key_auth_requested - -# "ssh": { -# "type": supported_cache_type, -# "port": Coerce(int), -# "user": str, -# "password": str, -# "ask_password": Bool, -# "passphrase": str, -# "ask_passphrase": Bool, -# "keyfile": str, -# "timeout": Coerce(int), -# "gss_auth": Bool, -# "allow_agent": Bool, -# "max_sessions": Coerce(int), -# Optional("verify", default=False): Bool, -# **REMOTE_COMMON, -# }, - - -def test_denied_user(ssh_server, mocker): + +def test_keyfile_set(ssh_server, mocker): f = SSHFileSystem( host=ssh_server["host"], port=ssh_server["port"], From 2f0e57e927dcf409cbbef5a8159de3a60545e115 Mon Sep 17 00:00:00 2001 From: Martin Wurzer Date: Sat, 2 May 2026 10:47:55 +0200 Subject: [PATCH 4/4] refactor inline dict; add comments for preferred_auth and tests; --- dvc_ssh/__init__.py | 19 +++++++++++-------- dvc_ssh/tests/test_conn.py | 4 ++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/dvc_ssh/__init__.py b/dvc_ssh/__init__.py index 4ce60db..3cf646e 100644 --- a/dvc_ssh/__init__.py +++ b/dvc_ssh/__init__.py @@ -65,19 +65,22 @@ def _prepare_credentials(self, **config): if port := config.get("port"): login_info["port"] = port - # in case preferred_auth is empty, fallback is publickey + # we can constrain which authentication methods are used, to avoid trying ones + # the user doesn't have configured, which can lead to input prompts and timeouts + # asyncssh: empty list is falsy, so methods are not constrained and all tried login_info["preferred_auth"] = [] - for option in ("password", "passphrase"): + # non exhaustive, maps which dvc config option uses which ssh auth method + _option_to_auth_methods = { + "password": "password", + "passphrase": "publickey", + } + + for option, auth_method in _option_to_auth_methods.items(): login_info[option] = config.get(option) if login_info[option] or config.get(f"ask_{option}"): - login_info["preferred_auth"].append( - { - "password": "password", - "passphrase": "publickey", - }[option] - ) + login_info["preferred_auth"].append(auth_method) if config.get(f"ask_{option}") and login_info[option] is None: login_info[option] = ask_password( diff --git a/dvc_ssh/tests/test_conn.py b/dvc_ssh/tests/test_conn.py index 73a7f4b..7deaea0 100644 --- a/dvc_ssh/tests/test_conn.py +++ b/dvc_ssh/tests/test_conn.py @@ -15,9 +15,13 @@ def test_keyfile_set(ssh_server, mocker): def test_password_set(ssh_server, mocker): + # make sure that we don't open a password prompt if password is set + # this would let the test fail with a timeout f = SSHFileSystem( host=ssh_server["host"], port=ssh_server["port"], + # see config in tests/docker-compose.yml, can't use environment variable here + # because it is not passed to the test process user="user", password="password", )