From c9e429c50d990051519114c5455d1eb13260b4bc Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Fri, 12 Jul 2019 17:38:26 +0100 Subject: [PATCH] Add support for specifing an explicit GSSAPI mech Add support for passing an explicit mech through to gssapi's `SecurityContext` constructor. This allows overriding the auto-detected mechanism, and enabling support for RFC4178 SPNEGO. Fixes #18 --- README.rst | 21 ++++++++++++++++++ requests_gssapi/gssapi_.py | 8 +++++-- test_requests_gssapi.py | 45 +++++++++++++++++++++++++++----------- 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index 0a651eb..73848ee 100644 --- a/README.rst +++ b/README.rst @@ -175,6 +175,27 @@ applicable). However, an explicit credential can be in instead, if desired. >>> r = requests.get("http://example.org", auth=gssapi_auth) ... +Explicit Mechanism +------------------ + +``HTTPSPNEGOAuth`` normally lets the underlying ``gssapi`` library decide which +negotiation mechanism to use. However, an explicit mechanism can be used instead +if desired. The ``mech`` parameter will be passed straight through to ``gssapi`` +without interference. It is expected to be an instance of ``gssapi.mechs.Mechanism``. + +.. code-block:: python + + >>> import gssapi + >>> import requests + >>> from requests_gssapi import HTTPSPNEGOAuth + >>> try: + ... spnego = gssapi,mechs.Mechanism.from_sasl_name("SPNEGO") + ... except AttributeError: + ... spnego = gssapi.OID.from_int_seq("1.3.6.1.5.5.2") + >>> gssapi_auth = HTTPSPNEGOAuth(mech=spnego) + >>> r = requests.get("http://example.org", auth=gssapi_auth) + ... + Delegation ---------- diff --git a/requests_gssapi/gssapi_.py b/requests_gssapi/gssapi_.py index 6a2ca95..2899126 100644 --- a/requests_gssapi/gssapi_.py +++ b/requests_gssapi/gssapi_.py @@ -98,13 +98,16 @@ class HTTPSPNEGOAuth(AuthBase): `creds` is GSSAPI credentials (gssapi.Credentials) to use for negotiation. Default is `None`. + `mech` is GSSAPI Mechanism (gssapi.Mechanism) to use for negotiation. + Default is `None` + `sanitize_mutual_error_response` controls whether we should clean up server responses. See the `SanitizedResponse` class. """ def __init__(self, mutual_authentication=DISABLED, target_name="HTTP", delegate=False, opportunistic_auth=False, creds=None, - sanitize_mutual_error_response=True): + mech=None, sanitize_mutual_error_response=True): self.context = {} self.pos = None self.mutual_authentication = mutual_authentication @@ -112,6 +115,7 @@ def __init__(self, mutual_authentication=DISABLED, target_name="HTTP", self.delegate = delegate self.opportunistic_auth = opportunistic_auth self.creds = creds + self.mech = mech self.sanitize_mutual_error_response = sanitize_mutual_error_response def generate_request_header(self, response, host, is_preemptive=False): @@ -139,7 +143,7 @@ def generate_request_header(self, response, host, is_preemptive=False): self.target_name, gssapi.NameType.hostbased_service) self.context[host] = gssapi.SecurityContext( usage="initiate", flags=gssflags, name=self.target_name, - creds=self.creds) + creds=self.creds, mech=self.mech) gss_stage = "stepping context" if is_preemptive: diff --git a/test_requests_gssapi.py b/test_requests_gssapi.py index 9014894..a4dbc21 100644 --- a/test_requests_gssapi.py +++ b/test_requests_gssapi.py @@ -106,7 +106,7 @@ def test_generate_request_header(self): b64_negotiate_response) fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - creds=None, flags=gssflags, usage="initiate") + creds=None, mech=None, flags=gssflags, usage="initiate") fake_resp.assert_called_with(b"token") def test_generate_request_header_init_error(self): @@ -121,7 +121,7 @@ def test_generate_request_header_init_error(self): auth.generate_request_header, response, host) fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - usage="initiate", flags=gssflags, creds=None) + usage="initiate", flags=gssflags, creds=None, mech=None) def test_generate_request_header_step_error(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, @@ -135,7 +135,7 @@ def test_generate_request_header_step_error(self): auth.generate_request_header, response, host) fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - usage="initiate", flags=gssflags, creds=None) + usage="initiate", flags=gssflags, creds=None, mech=None) fail_resp.assert_called_with(b"token") def test_authenticate_user(self): @@ -172,7 +172,7 @@ def test_authenticate_user(self): raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - flags=gssflags, usage="initiate", creds=None) + flags=gssflags, usage="initiate", creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_handle_401(self): @@ -209,7 +209,7 @@ def test_handle_401(self): raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - creds=None, flags=gssflags, usage="initiate") + creds=None, mech=None, flags=gssflags, usage="initiate") fake_resp.assert_called_with(b"token") def test_authenticate_server(self): @@ -448,7 +448,7 @@ def test_handle_response_401(self): raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - usage="initiate", flags=gssflags, creds=None) + usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_handle_response_401_rejected(self): @@ -491,7 +491,7 @@ def connection_send(self, *args, **kwargs): raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - usage="initiate", flags=gssflags, creds=None) + usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_generate_request_header_custom_service(self): @@ -505,7 +505,7 @@ def test_generate_request_header_custom_service(self): auth.generate_request_header(response, host), fake_init.assert_called_with( name=gssapi_name("barfoo@www.example.org"), - usage="initiate", flags=gssflags, creds=None) + usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_delegation(self): @@ -543,7 +543,7 @@ def test_delegation(self): raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - usage="initiate", flags=gssdelegflags, creds=None) + usage="initiate", flags=gssdelegflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_principal_override(self): @@ -561,7 +561,8 @@ def test_principal_override(self): name=gssapi_name("user@REALM")) fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - usage="initiate", flags=gssflags, creds=b"fake creds") + usage="initiate", flags=gssflags, + creds=b"fake creds", mech=None) def test_realm_override(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, @@ -575,7 +576,7 @@ def test_realm_override(self): auth.generate_request_header(response, host) fake_init.assert_called_with( name=gssapi_name("HTTP@otherhost.otherdomain.org"), - usage="initiate", flags=gssflags, creds=None) + usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_opportunistic_auth(self): @@ -604,7 +605,25 @@ def test_explicit_creds(self): auth.generate_request_header(response, host) fake_init.assert_called_with( name=gssapi_name("HTTP@www.example.org"), - usage="initiate", flags=gssflags, creds=b"fake creds") + usage="initiate", flags=gssflags, + creds=b"fake creds", mech=None) + fake_resp.assert_called_with(b"token") + + def test_explicit_mech(self): + with patch.multiple("gssapi.Credentials", __new__=fake_creds), \ + patch.multiple("gssapi.SecurityContext", __init__=fake_init, + step=fake_resp): + response = requests.Response() + response.url = "http://www.example.org/" + response.headers = {'www-authenticate': b64_negotiate_token} + host = urlparse(response.url).hostname + fake_mech = b'fake mech' + auth = requests_gssapi.HTTPSPNEGOAuth(mech=fake_mech) + auth.generate_request_header(response, host) + fake_init.assert_called_with( + name=gssapi_name("HTTP@www.example.org"), + usage="initiate", flags=gssflags, + creds=None, mech=b'fake mech') fake_resp.assert_called_with(b"token") def test_target_name(self): @@ -619,7 +638,7 @@ def test_target_name(self): auth.generate_request_header(response, host) fake_init.assert_called_with( name=gssapi_name("HTTP@otherhost.otherdomain.org"), - usage="initiate", flags=gssflags, creds=None) + usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token")