scmrepo 3.3.10__tar.gz → 3.3.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of scmrepo might be problematic. Click here for more details.
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.github/workflows/tests.yaml +2 -2
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.pre-commit-config.yaml +2 -2
- {scmrepo-3.3.10/src/scmrepo.egg-info → scmrepo-3.3.11}/PKG-INFO +4 -3
- {scmrepo-3.3.10 → scmrepo-3.3.11}/pyproject.toml +1 -1
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/dulwich/asyncssh_vendor.py +19 -65
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/pygit2/__init__.py +3 -1
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/pygit2/callbacks.py +2 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/objects.py +1 -1
- {scmrepo-3.3.10 → scmrepo-3.3.11/src/scmrepo.egg-info}/PKG-INFO +4 -3
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/requires.txt +1 -1
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_dulwich.py +39 -26
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.coveragerc +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.cruft.json +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.gitattributes +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.github/dependabot.yml +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.github/workflows/release.yaml +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.github/workflows/update-template.yaml +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/.gitignore +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/CODE_OF_CONDUCT.rst +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/CONTRIBUTING.rst +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/LICENSE +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/README.rst +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/noxfile.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/setup.cfg +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/__init__.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/asyn.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/base.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/exceptions.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/fs.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/__init__.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/__init__.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/base.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/dulwich/__init__.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/dulwich/client.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/gitpython.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/backend/pygit2/filter.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/config.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/credentials.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/__init__.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/client.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/exceptions.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/fetch.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/object.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/pointer.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/progress.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/smudge.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/lfs/storage.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/git/stash.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/noscm.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/progress.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/py.typed +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/urls.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo/utils.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/SOURCES.txt +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/dependency_links.txt +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/src/scmrepo.egg-info/top_level.txt +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/__init__.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/conftest.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/docker-compose.yml +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/git-init/git.sh +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_credentials.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_fs.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_git.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_lfs.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_noscm.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_pygit2.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_scmrepo.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_stash.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/test_urls.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/user.key +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/user.key.pub +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/vendor/__init__.py +0 -0
- {scmrepo-3.3.10 → scmrepo-3.3.11}/tests/vendor/test_paramiko_vendor.py +0 -0
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
strategy:
|
|
21
21
|
fail-fast: false
|
|
22
22
|
matrix:
|
|
23
|
-
os: [ubuntu-
|
|
23
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
24
24
|
pyv: ['3.9', '3.10', '3.11', '3.12', '3.13']
|
|
25
25
|
|
|
26
26
|
steps:
|
|
@@ -47,7 +47,7 @@ jobs:
|
|
|
47
47
|
run: nox -s tests-${{ matrix.pyv }} -- --slow --cov-report=xml
|
|
48
48
|
|
|
49
49
|
- name: Upload coverage report
|
|
50
|
-
uses: codecov/codecov-action@v5
|
|
50
|
+
uses: codecov/codecov-action@v5
|
|
51
51
|
|
|
52
52
|
- name: Build package
|
|
53
53
|
run: nox -s build
|
|
@@ -20,13 +20,13 @@ repos:
|
|
|
20
20
|
- id: sort-simple-yaml
|
|
21
21
|
- id: trailing-whitespace
|
|
22
22
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
23
|
-
rev: 'v0.
|
|
23
|
+
rev: 'v0.11.7'
|
|
24
24
|
hooks:
|
|
25
25
|
- id: ruff
|
|
26
26
|
args: [--fix, --exit-non-zero-on-fix]
|
|
27
27
|
- id: ruff-format
|
|
28
28
|
- repo: https://github.com/codespell-project/codespell
|
|
29
|
-
rev: v2.4.
|
|
29
|
+
rev: v2.4.1
|
|
30
30
|
hooks:
|
|
31
31
|
- id: codespell
|
|
32
32
|
additional_dependencies: ["tomli"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.11
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -38,12 +38,13 @@ Requires-Dist: pytest-sugar; extra == "tests"
|
|
|
38
38
|
Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
|
|
39
39
|
Requires-Dist: proxy.py; extra == "tests"
|
|
40
40
|
Provides-Extra: dev
|
|
41
|
-
Requires-Dist: mypy==1.
|
|
41
|
+
Requires-Dist: mypy==1.15.0; extra == "dev"
|
|
42
42
|
Requires-Dist: scmrepo[tests]; extra == "dev"
|
|
43
43
|
Requires-Dist: types-certifi; extra == "dev"
|
|
44
44
|
Requires-Dist: types-mock; extra == "dev"
|
|
45
45
|
Requires-Dist: types-paramiko; extra == "dev"
|
|
46
46
|
Requires-Dist: types-tqdm; extra == "dev"
|
|
47
|
+
Dynamic: license-file
|
|
47
48
|
|
|
48
49
|
scmrepo
|
|
49
50
|
=======
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import os
|
|
5
|
-
from collections.abc import Coroutine, Iterator
|
|
5
|
+
from collections.abc import Coroutine, Iterator
|
|
6
6
|
from typing import (
|
|
7
7
|
TYPE_CHECKING,
|
|
8
8
|
Any,
|
|
@@ -18,6 +18,7 @@ from scmrepo.asyn import BaseAsyncObject, sync_wrapper
|
|
|
18
18
|
from scmrepo.exceptions import AuthError
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Sequence
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
23
24
|
from asyncssh.auth import KbdIntPrompts, KbdIntResponse
|
|
@@ -40,6 +41,12 @@ async def _read_all(read: Callable[[int], Coroutine], n: Optional[int] = None) -
|
|
|
40
41
|
return b"".join(result)
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
async def _getpass(*args, **kwargs) -> str:
|
|
45
|
+
from getpass import getpass
|
|
46
|
+
|
|
47
|
+
return await asyncio.to_thread(getpass, *args, **kwargs)
|
|
48
|
+
|
|
49
|
+
|
|
43
50
|
class _StderrWrapper:
|
|
44
51
|
def __init__(self, stderr: "SSHReader", loop: asyncio.AbstractEventLoop) -> None:
|
|
45
52
|
self.stderr = stderr
|
|
@@ -97,45 +104,6 @@ class AsyncSSHWrapper(BaseAsyncObject):
|
|
|
97
104
|
close = sync_wrapper(_close)
|
|
98
105
|
|
|
99
106
|
|
|
100
|
-
# NOTE: Github's SSH server does not strictly comply with the SSH protocol.
|
|
101
|
-
# When validating a public key using the rsa-sha2-256 or rsa-sha2-512
|
|
102
|
-
# signature algorithms, RFC4252 + RFC8332 state that the server should respond
|
|
103
|
-
# with the same algorithm in SSH_MSG_USERAUTH_PK_OK. Github's server always
|
|
104
|
-
# returns "ssh-rsa" rather than the correct sha2 algorithm name (likely for
|
|
105
|
-
# backwards compatibility with old SSH client reasons). This behavior causes
|
|
106
|
-
# asyncssh to fail with a key-mismatch error (since asyncssh expects the server
|
|
107
|
-
# to behave properly).
|
|
108
|
-
#
|
|
109
|
-
# See also:
|
|
110
|
-
# https://www.ietf.org/rfc/rfc4252.txt
|
|
111
|
-
# https://www.ietf.org/rfc/rfc8332.txt
|
|
112
|
-
def _process_public_key_ok_gh(self, _pkttype, _pktid, packet):
|
|
113
|
-
from asyncssh.misc import ProtocolError
|
|
114
|
-
|
|
115
|
-
algorithm = packet.get_string()
|
|
116
|
-
key_data = packet.get_string()
|
|
117
|
-
packet.check_end()
|
|
118
|
-
|
|
119
|
-
# pylint: disable=protected-access
|
|
120
|
-
if (
|
|
121
|
-
(
|
|
122
|
-
algorithm == b"ssh-rsa"
|
|
123
|
-
and self._keypair.algorithm
|
|
124
|
-
not in (
|
|
125
|
-
b"ssh-rsa",
|
|
126
|
-
b"rsa-sha2-256",
|
|
127
|
-
b"rsa-sha2-512",
|
|
128
|
-
)
|
|
129
|
-
)
|
|
130
|
-
or (algorithm not in (b"ssh-rsa", self._keypair.algorithm))
|
|
131
|
-
or key_data != self._keypair.public_data
|
|
132
|
-
):
|
|
133
|
-
raise ProtocolError("Key mismatch")
|
|
134
|
-
|
|
135
|
-
self.create_task(self._send_signed_request())
|
|
136
|
-
return True
|
|
137
|
-
|
|
138
|
-
|
|
139
107
|
class InteractiveSSHClient(SSHClient):
|
|
140
108
|
_conn: Optional["SSHClientConnection"] = None
|
|
141
109
|
_keys_to_try: Optional[list["FilePath"]] = None
|
|
@@ -171,7 +139,7 @@ class InteractiveSSHClient(SSHClient):
|
|
|
171
139
|
self._keys_to_try = []
|
|
172
140
|
options = self._conn._options # pylint: disable=protected-access
|
|
173
141
|
config = options.config
|
|
174
|
-
client_keys = cast(Sequence[
|
|
142
|
+
client_keys = cast("Sequence[FilePath]", config.get("IdentityFile", ()))
|
|
175
143
|
if not client_keys:
|
|
176
144
|
client_keys = [
|
|
177
145
|
os.path.expanduser(os.path.join("~", ".ssh", path))
|
|
@@ -202,8 +170,6 @@ class InteractiveSSHClient(SSHClient):
|
|
|
202
170
|
return None
|
|
203
171
|
|
|
204
172
|
async def _read_private_key_interactive(self, path: "FilePath") -> "SSHKey":
|
|
205
|
-
from getpass import getpass
|
|
206
|
-
|
|
207
173
|
from asyncssh.public_key import (
|
|
208
174
|
KeyEncryptionError,
|
|
209
175
|
KeyImportError,
|
|
@@ -215,11 +181,8 @@ class InteractiveSSHClient(SSHClient):
|
|
|
215
181
|
if passphrase:
|
|
216
182
|
return read_private_key(path, passphrase=passphrase)
|
|
217
183
|
|
|
218
|
-
loop = asyncio.get_running_loop()
|
|
219
184
|
for _ in range(3):
|
|
220
|
-
passphrase = await
|
|
221
|
-
None, getpass, f"Enter passphrase for key '{path}': "
|
|
222
|
-
)
|
|
185
|
+
passphrase = await _getpass(f"Enter passphrase for key {path!r}: ")
|
|
223
186
|
if passphrase:
|
|
224
187
|
try:
|
|
225
188
|
key = read_private_key(path, passphrase=passphrase)
|
|
@@ -239,23 +202,20 @@ class InteractiveSSHClient(SSHClient):
|
|
|
239
202
|
lang: str,
|
|
240
203
|
prompts: "KbdIntPrompts",
|
|
241
204
|
) -> Optional["KbdIntResponse"]:
|
|
242
|
-
from getpass import getpass
|
|
243
|
-
|
|
244
205
|
if os.environ.get("GIT_TERMINAL_PROMPT") == "0":
|
|
245
206
|
return None
|
|
246
207
|
|
|
247
|
-
def _getpass(prompt: str) -> str:
|
|
248
|
-
return getpass(prompt=prompt).rstrip()
|
|
249
|
-
|
|
250
208
|
if instructions:
|
|
251
209
|
pass
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
|
|
210
|
+
|
|
211
|
+
response: list[str] = []
|
|
212
|
+
for prompt, _echo in prompts:
|
|
213
|
+
p = await _getpass(f"({name}) {prompt}" if name else prompt)
|
|
214
|
+
response.append(p.rstrip())
|
|
215
|
+
return response
|
|
216
|
+
|
|
217
|
+
async def password_auth_requested(self) -> str:
|
|
218
|
+
return await _getpass()
|
|
259
219
|
|
|
260
220
|
|
|
261
221
|
class AsyncSSHVendor(BaseAsyncObject, SSHVendor):
|
|
@@ -286,12 +246,6 @@ class AsyncSSHVendor(BaseAsyncObject, SSHVendor):
|
|
|
286
246
|
key_filename: Optional path to private keyfile
|
|
287
247
|
"""
|
|
288
248
|
import asyncssh
|
|
289
|
-
from asyncssh.auth import MSG_USERAUTH_PK_OK, _ClientPublicKeyAuth
|
|
290
|
-
|
|
291
|
-
# pylint: disable=protected-access
|
|
292
|
-
_ClientPublicKeyAuth._packet_handlers[MSG_USERAUTH_PK_OK] = (
|
|
293
|
-
_process_public_key_ok_gh
|
|
294
|
-
)
|
|
295
249
|
|
|
296
250
|
try:
|
|
297
251
|
conn = await asyncssh.connect(
|
|
@@ -289,7 +289,9 @@ class Pygit2Backend(BaseGitBackend): # pylint:disable=abstract-method
|
|
|
289
289
|
bare = True
|
|
290
290
|
try:
|
|
291
291
|
with RemoteCallbacks(progress=progress) as cb:
|
|
292
|
-
repo = clone_repository(
|
|
292
|
+
repo = clone_repository(
|
|
293
|
+
url, os.fspath(to_path), callbacks=cb, bare=bare
|
|
294
|
+
)
|
|
293
295
|
if mirror:
|
|
294
296
|
cls._set_mirror(repo, progress=progress)
|
|
295
297
|
except GitError as exc:
|
|
@@ -66,6 +66,8 @@ class RemoteCallbacks(_RemoteCallbacks, AbstractContextManager):
|
|
|
66
66
|
else:
|
|
67
67
|
creds = Credential(username=username_from_url, url=url).fill()
|
|
68
68
|
self._store_credentials = creds
|
|
69
|
+
assert creds.username is not None
|
|
70
|
+
assert creds.password is not None
|
|
69
71
|
return UserPass(creds.username, creds.password)
|
|
70
72
|
except CredentialNotFoundError:
|
|
71
73
|
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: scmrepo
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.11
|
|
4
4
|
Summary: scmrepo
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -38,12 +38,13 @@ Requires-Dist: pytest-sugar; extra == "tests"
|
|
|
38
38
|
Requires-Dist: pytest-test-utils<0.2,>=0.1.0; extra == "tests"
|
|
39
39
|
Requires-Dist: proxy.py; extra == "tests"
|
|
40
40
|
Provides-Extra: dev
|
|
41
|
-
Requires-Dist: mypy==1.
|
|
41
|
+
Requires-Dist: mypy==1.15.0; extra == "dev"
|
|
42
42
|
Requires-Dist: scmrepo[tests]; extra == "dev"
|
|
43
43
|
Requires-Dist: types-certifi; extra == "dev"
|
|
44
44
|
Requires-Dist: types-mock; extra == "dev"
|
|
45
45
|
Requires-Dist: types-paramiko; extra == "dev"
|
|
46
46
|
Requires-Dist: types-tqdm; extra == "dev"
|
|
47
|
+
Dynamic: license-file
|
|
47
48
|
|
|
48
49
|
scmrepo
|
|
49
50
|
=======
|
|
@@ -8,6 +8,7 @@ from unittest.mock import AsyncMock
|
|
|
8
8
|
import asyncssh
|
|
9
9
|
import paramiko
|
|
10
10
|
import pytest
|
|
11
|
+
from paramiko.server import InteractiveQuery
|
|
11
12
|
from pytest_mock import MockerFixture
|
|
12
13
|
from pytest_test_utils.waiters import wait_until
|
|
13
14
|
|
|
@@ -52,13 +53,24 @@ class Server(paramiko.ServerInterface):
|
|
|
52
53
|
"""http://docs.paramiko.org/en/2.4/api/server.html."""
|
|
53
54
|
|
|
54
55
|
def __init__(self, commands, *args, **kwargs) -> None:
|
|
55
|
-
super().__init__(
|
|
56
|
+
super().__init__()
|
|
56
57
|
self.commands = commands
|
|
58
|
+
self.allowed_auths = kwargs.get("allowed_auths", "publickey,password")
|
|
57
59
|
|
|
58
60
|
def check_channel_exec_request(self, channel, command):
|
|
59
61
|
self.commands.append(command)
|
|
60
62
|
return True
|
|
61
63
|
|
|
64
|
+
def check_auth_interactive(self, username: str, submethods: str):
|
|
65
|
+
return InteractiveQuery(
|
|
66
|
+
"Password", "Enter the password", f"Password for user {USER}:"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def check_auth_interactive_response(self, responses):
|
|
70
|
+
if responses[0] == PASSWORD:
|
|
71
|
+
return paramiko.AUTH_SUCCESSFUL
|
|
72
|
+
return paramiko.AUTH_FAILED
|
|
73
|
+
|
|
62
74
|
def check_auth_password(self, username, password):
|
|
63
75
|
if username == USER and password == PASSWORD:
|
|
64
76
|
return paramiko.AUTH_SUCCESSFUL
|
|
@@ -76,12 +88,12 @@ class Server(paramiko.ServerInterface):
|
|
|
76
88
|
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
|
77
89
|
|
|
78
90
|
def get_allowed_auths(self, username):
|
|
79
|
-
return
|
|
91
|
+
return self.allowed_auths
|
|
80
92
|
|
|
81
93
|
|
|
82
94
|
@pytest.fixture
|
|
83
95
|
def ssh_conn(request: pytest.FixtureRequest) -> dict[str, Any]:
|
|
84
|
-
server = Server([])
|
|
96
|
+
server = Server([], **getattr(request, "param", {}))
|
|
85
97
|
|
|
86
98
|
socket.setdefaulttimeout(10)
|
|
87
99
|
request.addfinalizer(lambda: socket.setdefaulttimeout(None))
|
|
@@ -133,7 +145,8 @@ def test_run_command_password(server: Server, ssh_port: int):
|
|
|
133
145
|
assert b"test_run_command_password" in server.commands
|
|
134
146
|
|
|
135
147
|
|
|
136
|
-
|
|
148
|
+
@pytest.mark.parametrize("ssh_conn", [{"allowed_auths": "publickey"}], indirect=True)
|
|
149
|
+
def test_run_command_no_password(ssh_port: int):
|
|
137
150
|
vendor = AsyncSSHVendor()
|
|
138
151
|
with pytest.raises(AuthError):
|
|
139
152
|
vendor.run_command(
|
|
@@ -145,6 +158,28 @@ def test_run_command_no_password(server: Server, ssh_port: int):
|
|
|
145
158
|
)
|
|
146
159
|
|
|
147
160
|
|
|
161
|
+
@pytest.mark.parametrize(
|
|
162
|
+
"ssh_conn",
|
|
163
|
+
[{"allowed_auths": "password"}, {"allowed_auths": "keyboard-interactive"}],
|
|
164
|
+
indirect=True,
|
|
165
|
+
ids=["password", "interactive"],
|
|
166
|
+
)
|
|
167
|
+
def test_should_prompt_for_password_when_no_password_passed(
|
|
168
|
+
mocker: MockerFixture, server: Server, ssh_port: int
|
|
169
|
+
):
|
|
170
|
+
mocked_getpass = mocker.patch("getpass.getpass", return_value=PASSWORD)
|
|
171
|
+
vendor = AsyncSSHVendor()
|
|
172
|
+
vendor.run_command(
|
|
173
|
+
"127.0.0.1",
|
|
174
|
+
"test_run_command_password",
|
|
175
|
+
username=USER,
|
|
176
|
+
port=ssh_port,
|
|
177
|
+
password=None,
|
|
178
|
+
)
|
|
179
|
+
assert server.commands == [b"test_run_command_password"]
|
|
180
|
+
mocked_getpass.asssert_called_once()
|
|
181
|
+
|
|
182
|
+
|
|
148
183
|
def test_run_command_with_privkey(server: Server, ssh_port: int):
|
|
149
184
|
key = asyncssh.import_private_key(CLIENT_KEY)
|
|
150
185
|
|
|
@@ -212,28 +247,6 @@ def test_run_command_partial_transfer(ssh_port: int, mocker: MockerFixture):
|
|
|
212
247
|
assert mock_stderr.call_count == 3
|
|
213
248
|
|
|
214
249
|
|
|
215
|
-
@pytest.mark.parametrize("algorithm", [b"ssh-rsa", b"rsa-sha2-256", b"rsa-sha2-512"])
|
|
216
|
-
def test_dulwich_github_compat(mocker: MockerFixture, algorithm: bytes):
|
|
217
|
-
from asyncssh.misc import ProtocolError
|
|
218
|
-
|
|
219
|
-
from scmrepo.git.backend.dulwich.asyncssh_vendor import _process_public_key_ok_gh
|
|
220
|
-
|
|
221
|
-
key_data = b"foo"
|
|
222
|
-
auth = mocker.Mock(
|
|
223
|
-
_keypair=mocker.Mock(algorithm=algorithm, public_data=key_data),
|
|
224
|
-
)
|
|
225
|
-
packet = mocker.Mock()
|
|
226
|
-
|
|
227
|
-
strings = iter((b"ed21556", key_data))
|
|
228
|
-
packet.get_string = lambda: next(strings)
|
|
229
|
-
with pytest.raises(ProtocolError):
|
|
230
|
-
_process_public_key_ok_gh(auth, None, None, packet)
|
|
231
|
-
|
|
232
|
-
strings = iter((b"ssh-rsa", key_data))
|
|
233
|
-
packet.get_string = lambda: next(strings)
|
|
234
|
-
_process_public_key_ok_gh(auth, None, None, packet)
|
|
235
|
-
|
|
236
|
-
|
|
237
250
|
@pytest.mark.skipif(os.name != "nt", reason="Windows only")
|
|
238
251
|
def test_git_bash_ssh_vendor(mocker):
|
|
239
252
|
from dulwich.client import SubprocessSSHVendor
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|