diff options
author | Jeff Forcier <jeff@bitprophet.org> | 2018-03-12 17:51:13 -0700 |
---|---|---|
committer | Jeff Forcier <jeff@bitprophet.org> | 2018-05-03 18:12:58 -0700 |
commit | 91cbbfc17fb8f38ea6e86bb39e36acd407ef3cef (patch) | |
tree | e23780b5f66346a9c662042a5ab211609947df01 | |
parent | 6906398c39364d63781acf8e02f9333292afb03a (diff) | |
download | paramiko-auth-shakeup.tar.gz |
WIPauth-shakeup
-rw-r--r-- | paramiko/__init__.py | 1 | ||||
-rw-r--r-- | paramiko/authenticator.py | 74 | ||||
-rw-r--r-- | tests/conftest.py | 12 | ||||
-rw-r--r-- | tests/test_util.py | 1 |
4 files changed, 87 insertions, 1 deletions
diff --git a/paramiko/__init__.py b/paramiko/__init__.py index d646e904..b851c361 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -24,6 +24,7 @@ from paramiko.client import ( SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, WarningPolicy, ) +from paramiko.authenticator import Authenticator from paramiko.auth_handler import AuthHandler from paramiko.ssh_gss import GSSAuth, GSS_AUTH_AVAILABLE, GSS_EXCEPTIONS from paramiko.channel import Channel, ChannelFile diff --git a/paramiko/authenticator.py b/paramiko/authenticator.py new file mode 100644 index 00000000..91377e15 --- /dev/null +++ b/paramiko/authenticator.py @@ -0,0 +1,74 @@ +""" +High level authentication logic module. + +Largely replaces what used to be implemented solely within `SSHClient` and its +``_auth()`` method. + +Technically, this module's main API member - `Authenticator` - sits below +`SSHClient` (meaning it can be used by non-Client-based code) and above +`Transport` (which provides the bare auth SSH message 'levers' only.) + +.. note:: + This is not to be confused with the `paramiko.auth_handler` module, which + sits *below* (or within) `Transport`, handling the low level guts of + submitting authentication protocol messages and awaiting their responses. +""" + +class Authenticator(object): + """ + Wraps a `Transport` and uses it to authenticate or die trying. + + Lifecycle is relatively straightforward: + + - Instantiate with a handle onto a `Transport` object. This object must + already have been prepared for authentication by calling + `Transport.start_client`. + - Call the instance's `authenticate_with_kwargs` method with as many or few + auth-source keyword arguments as needed, which will: + - attempt to authenticate in a documented order of preference + - if successful, return an `AuthenticationResult` + - if unsuccessful or if additional auth factors are required, raise an + `AuthenticationException` (or subclass thereof) which will exhibit a + ``.result`` attribute whose value is an `AuthenticationResult`. + - either way, the point is that the caller will have access to an + `AuthenticationResult` object exposing the various auth sources + tried, what order they were tried in, and what the result was. + - see API docs for `authenticate` for further details. + - Alternately, for tighter control of which auth sources are tried and in + what order, call `authenticate` directly (it's what implements the guts + of `authenticate_with_kwargs`) which foregoes most kwargs in lieu of an + iterable containing `AuthSource` objects. + """ + def __init__(self, transport): + # TODO: probably sanity check transport state and bail early if it's + # not ready. + # TODO: consider adding some more of SSHClient.connect (optionally, if + # the caller didn't already do these things) like the call to + # .start_client; then update lifecycle in docstring. + pass + + def authenticate_with_kwargs(self, lots_o_kwargs_here): + # Basically SSHClient._auth signature...then calls + # sources_from_kwargs() and stuffs result into authenticate()? + # TODO: at the start, just copypasta/tweak SSHClient._auth so the + # break-up is tested; THEN move to the newer cleaner shit? + # TODO: this is probably a good spot to reject the + # password-as-passphrase bit; accept distinct kwargs and require + # SSHClient to implement the fallback on its end. + pass + + def authenticate(self, username, *sources): + # TODO: define AuthSource (maybe rename...lol), should be lightweight, + # pairing an auth type with some value or iterable of values + # TODO: implement cleaner version of SSHClient._auth, somehow, that + # handles multi-factor auth much better than the current shite + # trickledown. (Be very TDD here...! Perhaps wait until single-source + # tests all pass first, then can ensure they continue to do so?) + pass + + def sources_from_kwargs(self, kwargs): + # TODO: **kwargs? whatever, this is mostly internal + # TODO: this should implement, and document, the current (and/or then + # desired) way that a pile of kwargs becomes an ordered set of + # attempted auths... + pass diff --git a/tests/conftest.py b/tests/conftest.py index b4095a3c..a90000fe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import shutil import threading import pytest -from paramiko import RSAKey, SFTPServer, SFTP, Transport +from paramiko import RSAKey, SFTPServer, SFTP, Transport, Authenticator from ._loop import LoopSocket from ._stub_sftp import StubServer, StubSFTPServer @@ -62,6 +62,16 @@ def trans(): sockc.close() +@pytest.fixture +def authn(trans): + """ + Yields an `Authenticator` wrapping a `Transport` (from `trans`.) + """ + # TODO: call start_client() on the trans too? or push that into + # `Authenticator` itself? + yield Authenticator(trans) + + def make_sftp_folder(): """ Ensure expected target temp folder exists on the remote end. diff --git a/tests/test_util.py b/tests/test_util.py index 5db06dae..3385c5ac 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -73,6 +73,7 @@ class UtilTest(unittest.TestCase): Agent AgentKey AuthenticationException + Authenticator AutoAddPolicy BadAuthenticationType BufferedFile |