diff options
author | Jeff Forcier <jeff@bitprophet.org> | 2023-05-18 19:23:19 -0400 |
---|---|---|
committer | Jeff Forcier <jeff@bitprophet.org> | 2023-05-22 12:22:05 -0400 |
commit | 90985d63bfe06f5bcb508cca74f9c135f881583c (patch) | |
tree | 5523c1dfaee8d1b1fbb925b6da5b2e58f6ff86ff | |
parent | ce454580c03997d9b5873fe31e1ce27d1c64cf12 (diff) |
Test existing AuthSource class tree
Includes relevant tweaks to assorted classes
-rw-r--r-- | paramiko/__init__.py | 5 | ||||
-rw-r--r-- | paramiko/auth_strategy.py | 11 | ||||
-rw-r--r-- | paramiko/pkey.py | 2 | ||||
-rw-r--r-- | tests/auth.py | 103 | ||||
-rw-r--r-- | tests/test_util.py | 6 |
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", |