summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2023-05-18 19:23:19 -0400
committerJeff Forcier <jeff@bitprophet.org>2023-05-22 12:22:05 -0400
commit90985d63bfe06f5bcb508cca74f9c135f881583c (patch)
tree5523c1dfaee8d1b1fbb925b6da5b2e58f6ff86ff
parentce454580c03997d9b5873fe31e1ce27d1c64cf12 (diff)
Test existing AuthSource class tree
Includes relevant tweaks to assorted classes
-rw-r--r--paramiko/__init__.py5
-rw-r--r--paramiko/auth_strategy.py11
-rw-r--r--paramiko/pkey.py2
-rw-r--r--tests/auth.py103
-rw-r--r--tests/test_util.py6
5 files changed, 121 insertions, 6 deletions
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 4bb261e0..aa1463e7 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -36,7 +36,12 @@ from paramiko.auth_strategy import (
AuthStrategy,
AuthResult,
AuthSource,
+ InMemoryPrivateKey,
NoneAuth,
+ OnDiskPrivateKey,
+ Password,
+ PrivateKey,
+ SourceResult,
)
from paramiko.ssh_gss import GSSAuth, GSS_AUTH_AVAILABLE, GSS_EXCEPTIONS
from paramiko.channel import (
diff --git a/paramiko/auth_strategy.py b/paramiko/auth_strategy.py
index bbb52141..524ae349 100644
--- a/paramiko/auth_strategy.py
+++ b/paramiko/auth_strategy.py
@@ -81,6 +81,10 @@ class Password(AuthSource):
return transport.auth_password(self.username, password)
+# TODO 4.0: twiddle this, or PKey, or both, so they're more obviously distinct.
+# TODO 4.0: the obvious is to make this more wordy (PrivateKeyAuth), the
+# minimalist approch might be to rename PKey to just Key (esp given all the
+# subclasses are WhateverKey and not WhateverPKey)
class PrivateKey(AuthSource):
"""
Essentially a mixin for private keys.
@@ -130,13 +134,12 @@ class OnDiskPrivateKey(PrivateKey):
The `PKey` object this auth source uses/represents.
"""
- # TODO: how to log/note how this path came to our attention (ssh_config,
- # fabric config, some direct kwarg somewhere, CLI flag, etc)? Different
- # subclasses for all of those seems like massive overkill, so just some
- # sort of "via" or "source" string argument?
def __init__(self, username, source, path, pkey):
super().__init__(username=username)
self.source = source
+ allowed = ("ssh-config", "python-config", "implicit-home")
+ if source not in allowed:
+ raise ValueError(f"source argument must be one of: {allowed!r}")
self.path = path
# Superclass wants .pkey, other two are mostly for display/debugging.
self.pkey = pkey
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index 26c11e95..4f9f6d57 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -252,7 +252,7 @@ class PKey:
def __repr__(self):
comment = ""
# Works for AgentKey, may work for others?
- if hasattr(self, "comment"):
+ if hasattr(self, "comment") and self.comment:
comment = f", comment={self.comment!r}"
return f"{self.__class__.__name__}(alg={self.algorithm_name}, bits={self.get_bits()}, fp={self.fingerprint}{comment})" # noqa
diff --git a/tests/auth.py b/tests/auth.py
index eacaf210..4c901c75 100644
--- a/tests/auth.py
+++ b/tests/auth.py
@@ -9,11 +9,16 @@ from unittest.mock import Mock
from pytest import raises
from paramiko import (
+ AgentKey,
AuthenticationException,
AuthSource,
BadAuthenticationType,
DSSKey,
+ InMemoryPrivateKey,
NoneAuth,
+ OnDiskPrivateKey,
+ Password,
+ PrivateKey,
PKey,
RSAKey,
SSHException,
@@ -345,8 +350,104 @@ class AuthSource_:
class NoneAuth_:
def authenticate_auths_none(self):
trans = Mock()
- NoneAuth("foo").authenticate(trans)
+ result = NoneAuth("foo").authenticate(trans)
trans.auth_none.assert_called_once_with("foo")
+ assert result is trans.auth_none.return_value
+
+ def repr_shows_class(self):
+ assert repr(NoneAuth("foo")) == "NoneAuth()"
+
+ class Password_:
+ def init_takes_and_stores_password_getter(self):
+ with raises(TypeError):
+ Password("foo")
+ getter = Mock()
+ pw = Password("foo", password_getter=getter)
+ assert pw.password_getter is getter
+
+ def repr_adds_username(self):
+ pw = Password("foo", password_getter=Mock())
+ assert repr(pw) == "Password(user='foo')"
+
+ def authenticate_gets_and_supplies_password(self):
+ getter = Mock(return_value="bar")
+ trans = Mock()
+ pw = Password("foo", password_getter=getter)
+ result = pw.authenticate(trans)
+ trans.auth_password.assert_called_once_with("foo", "bar")
+ assert result is trans.auth_password.return_value
+
+ class PrivateKey_:
+ def authenticate_calls_publickey_with_pkey(self):
+ source = PrivateKey(username="foo")
+ source.pkey = Mock() # set by subclasses
+ trans = Mock()
+ result = source.authenticate(trans)
+ trans.auth_publickey.assert_called_once_with("foo", source.pkey)
+ assert result is trans.auth_publickey.return_value
+
+ class InMemoryPrivateKey_:
+ def init_takes_pkey_object(self):
+ with raises(TypeError):
+ InMemoryPrivateKey("foo")
+ pkey = Mock()
+ source = InMemoryPrivateKey(username="foo", pkey=pkey)
+ assert source.pkey is pkey
+
+ def repr_shows_pkey_repr(self):
+ pkey = PKey.from_path(_support("ed25519.key"))
+ source = InMemoryPrivateKey("foo", pkey)
+ assert (
+ repr(source)
+ == "InMemoryPrivateKey(pkey=Ed25519Key(alg=ED25519, bits=256, fp=SHA256:J6VESFdD3xSChn8y9PzWzeF+1tl892mOy2TqkMLO4ow))" # noqa
+ )
+
+ def repr_appends_agent_flag_when_AgentKey(self):
+ real_key = PKey.from_path(_support("ed25519.key"))
+ pkey = AgentKey(agent=None, blob=bytes(real_key))
+ source = InMemoryPrivateKey("foo", pkey)
+ assert (
+ repr(source)
+ == "InMemoryPrivateKey(pkey=AgentKey(alg=ED25519, bits=256, fp=SHA256:J6VESFdD3xSChn8y9PzWzeF+1tl892mOy2TqkMLO4ow)) [agent]" # noqa
+ )
+
+ class OnDiskPrivateKey_:
+ def init_takes_source_path_and_pkey(self):
+ with raises(TypeError):
+ OnDiskPrivateKey("foo")
+ with raises(TypeError):
+ OnDiskPrivateKey("foo", "bar")
+ with raises(TypeError):
+ OnDiskPrivateKey("foo", "bar", "biz")
+ source = OnDiskPrivateKey(
+ username="foo",
+ source="ssh-config",
+ path="of-exile",
+ pkey="notreally",
+ )
+ assert source.username == "foo"
+ assert source.source == "ssh-config"
+ assert source.path == "of-exile"
+ assert source.pkey == "notreally"
+
+ def init_requires_specific_value_for_source(self):
+ with raises(
+ ValueError,
+ match=r"source argument must be one of: \('ssh-config', 'python-config', 'implicit-home'\)", # noqa
+ ):
+ OnDiskPrivateKey("foo", source="what?", path="meh", pkey="no")
+
+ def repr_reflects_source_path_and_pkey(self):
+ source = OnDiskPrivateKey(
+ username="foo",
+ source="ssh-config",
+ path="of-exile",
+ pkey="notreally",
+ )
+ assert (
+ repr(source)
+ == "OnDiskPrivateKey(key='notreally', source='ssh-config', path='of-exile')" # noqa
+ )
class AuthStrategy_:
diff --git a/tests/test_util.py b/tests/test_util.py
index 4a8cf972..060e6249 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -50,6 +50,7 @@ class UtilTest(unittest.TestCase):
"AgentKey",
"AuthenticationException",
"AuthHandler",
+ "AuthResult",
"AuthSource",
"AuthStrategy",
"AutoAddPolicy",
@@ -61,10 +62,14 @@ class UtilTest(unittest.TestCase):
"CouldNotCanonicalize",
"DSSKey",
"HostKeys",
+ "InMemoryPrivateKey",
"Message",
"MissingHostKeyPolicy",
"NoneAuth",
+ "OnDiskPrivateKey",
+ "Password",
"PasswordRequiredException",
+ "PrivateKey",
"RSAKey",
"RejectPolicy",
"SFTP",
@@ -81,6 +86,7 @@ class UtilTest(unittest.TestCase):
"SSHException",
"SecurityOptions",
"ServerInterface",
+ "SourceResult",
"SubsystemHandler",
"Transport",
"WarningPolicy",