summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2018-03-12 17:51:13 -0700
committerJeff Forcier <jeff@bitprophet.org>2018-05-03 18:12:58 -0700
commit91cbbfc17fb8f38ea6e86bb39e36acd407ef3cef (patch)
treee23780b5f66346a9c662042a5ab211609947df01
parent6906398c39364d63781acf8e02f9333292afb03a (diff)
downloadparamiko-auth-shakeup.tar.gz
-rw-r--r--paramiko/__init__.py1
-rw-r--r--paramiko/authenticator.py74
-rw-r--r--tests/conftest.py12
-rw-r--r--tests/test_util.py1
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