Skip to content

Commit

Permalink
Add support for specifing an explicit GSSAPI mech
Browse files Browse the repository at this point in the history
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
  • Loading branch information
optiz0r committed Dec 10, 2019
1 parent c90ea11 commit ddcec4f
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 15 deletions.
21 changes: 21 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------

Expand Down
8 changes: 6 additions & 2 deletions requests_gssapi/gssapi_.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,24 @@ 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
self.target_name = target_name
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):
Expand Down Expand Up @@ -140,7 +144,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:
Expand Down
45 changes: 32 additions & 13 deletions test_requests_gssapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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,
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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,
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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")


Expand Down

0 comments on commit ddcec4f

Please sign in to comment.