Skip to content

Commit c9e429c

Browse files
optiz0rfrozencemetery
authored andcommitted
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
1 parent dba7c1c commit c9e429c

File tree

3 files changed

+59
-15
lines changed

3 files changed

+59
-15
lines changed

Diff for: README.rst

+21
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,27 @@ applicable). However, an explicit credential can be in instead, if desired.
175175
>>> r = requests.get("http://example.org", auth=gssapi_auth)
176176
...
177177
178+
Explicit Mechanism
179+
------------------
180+
181+
``HTTPSPNEGOAuth`` normally lets the underlying ``gssapi`` library decide which
182+
negotiation mechanism to use. However, an explicit mechanism can be used instead
183+
if desired. The ``mech`` parameter will be passed straight through to ``gssapi``
184+
without interference. It is expected to be an instance of ``gssapi.mechs.Mechanism``.
185+
186+
.. code-block:: python
187+
188+
>>> import gssapi
189+
>>> import requests
190+
>>> from requests_gssapi import HTTPSPNEGOAuth
191+
>>> try:
192+
... spnego = gssapi,mechs.Mechanism.from_sasl_name("SPNEGO")
193+
... except AttributeError:
194+
... spnego = gssapi.OID.from_int_seq("1.3.6.1.5.5.2")
195+
>>> gssapi_auth = HTTPSPNEGOAuth(mech=spnego)
196+
>>> r = requests.get("http://example.org", auth=gssapi_auth)
197+
...
198+
178199
Delegation
179200
----------
180201

Diff for: requests_gssapi/gssapi_.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,24 @@ class HTTPSPNEGOAuth(AuthBase):
9898
`creds` is GSSAPI credentials (gssapi.Credentials) to use for negotiation.
9999
Default is `None`.
100100
101+
`mech` is GSSAPI Mechanism (gssapi.Mechanism) to use for negotiation.
102+
Default is `None`
103+
101104
`sanitize_mutual_error_response` controls whether we should clean up
102105
server responses. See the `SanitizedResponse` class.
103106
104107
"""
105108
def __init__(self, mutual_authentication=DISABLED, target_name="HTTP",
106109
delegate=False, opportunistic_auth=False, creds=None,
107-
sanitize_mutual_error_response=True):
110+
mech=None, sanitize_mutual_error_response=True):
108111
self.context = {}
109112
self.pos = None
110113
self.mutual_authentication = mutual_authentication
111114
self.target_name = target_name
112115
self.delegate = delegate
113116
self.opportunistic_auth = opportunistic_auth
114117
self.creds = creds
118+
self.mech = mech
115119
self.sanitize_mutual_error_response = sanitize_mutual_error_response
116120

117121
def generate_request_header(self, response, host, is_preemptive=False):
@@ -139,7 +143,7 @@ def generate_request_header(self, response, host, is_preemptive=False):
139143
self.target_name, gssapi.NameType.hostbased_service)
140144
self.context[host] = gssapi.SecurityContext(
141145
usage="initiate", flags=gssflags, name=self.target_name,
142-
creds=self.creds)
146+
creds=self.creds, mech=self.mech)
143147

144148
gss_stage = "stepping context"
145149
if is_preemptive:

Diff for: test_requests_gssapi.py

+32-13
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def test_generate_request_header(self):
106106
b64_negotiate_response)
107107
fake_init.assert_called_with(
108108
name=gssapi_name("[email protected]"),
109-
creds=None, flags=gssflags, usage="initiate")
109+
creds=None, mech=None, flags=gssflags, usage="initiate")
110110
fake_resp.assert_called_with(b"token")
111111

112112
def test_generate_request_header_init_error(self):
@@ -121,7 +121,7 @@ def test_generate_request_header_init_error(self):
121121
auth.generate_request_header, response, host)
122122
fake_init.assert_called_with(
123123
name=gssapi_name("[email protected]"),
124-
usage="initiate", flags=gssflags, creds=None)
124+
usage="initiate", flags=gssflags, creds=None, mech=None)
125125

126126
def test_generate_request_header_step_error(self):
127127
with patch.multiple("gssapi.SecurityContext", __init__=fake_init,
@@ -135,7 +135,7 @@ def test_generate_request_header_step_error(self):
135135
auth.generate_request_header, response, host)
136136
fake_init.assert_called_with(
137137
name=gssapi_name("[email protected]"),
138-
usage="initiate", flags=gssflags, creds=None)
138+
usage="initiate", flags=gssflags, creds=None, mech=None)
139139
fail_resp.assert_called_with(b"token")
140140

141141
def test_authenticate_user(self):
@@ -172,7 +172,7 @@ def test_authenticate_user(self):
172172
raw.release_conn.assert_called_with()
173173
fake_init.assert_called_with(
174174
name=gssapi_name("[email protected]"),
175-
flags=gssflags, usage="initiate", creds=None)
175+
flags=gssflags, usage="initiate", creds=None, mech=None)
176176
fake_resp.assert_called_with(b"token")
177177

178178
def test_handle_401(self):
@@ -209,7 +209,7 @@ def test_handle_401(self):
209209
raw.release_conn.assert_called_with()
210210
fake_init.assert_called_with(
211211
name=gssapi_name("[email protected]"),
212-
creds=None, flags=gssflags, usage="initiate")
212+
creds=None, mech=None, flags=gssflags, usage="initiate")
213213
fake_resp.assert_called_with(b"token")
214214

215215
def test_authenticate_server(self):
@@ -448,7 +448,7 @@ def test_handle_response_401(self):
448448
raw.release_conn.assert_called_with()
449449
fake_init.assert_called_with(
450450
name=gssapi_name("[email protected]"),
451-
usage="initiate", flags=gssflags, creds=None)
451+
usage="initiate", flags=gssflags, creds=None, mech=None)
452452
fake_resp.assert_called_with(b"token")
453453

454454
def test_handle_response_401_rejected(self):
@@ -491,7 +491,7 @@ def connection_send(self, *args, **kwargs):
491491
raw.release_conn.assert_called_with()
492492
fake_init.assert_called_with(
493493
name=gssapi_name("[email protected]"),
494-
usage="initiate", flags=gssflags, creds=None)
494+
usage="initiate", flags=gssflags, creds=None, mech=None)
495495
fake_resp.assert_called_with(b"token")
496496

497497
def test_generate_request_header_custom_service(self):
@@ -505,7 +505,7 @@ def test_generate_request_header_custom_service(self):
505505
auth.generate_request_header(response, host),
506506
fake_init.assert_called_with(
507507
name=gssapi_name("[email protected]"),
508-
usage="initiate", flags=gssflags, creds=None)
508+
usage="initiate", flags=gssflags, creds=None, mech=None)
509509
fake_resp.assert_called_with(b"token")
510510

511511
def test_delegation(self):
@@ -543,7 +543,7 @@ def test_delegation(self):
543543
raw.release_conn.assert_called_with()
544544
fake_init.assert_called_with(
545545
name=gssapi_name("[email protected]"),
546-
usage="initiate", flags=gssdelegflags, creds=None)
546+
usage="initiate", flags=gssdelegflags, creds=None, mech=None)
547547
fake_resp.assert_called_with(b"token")
548548

549549
def test_principal_override(self):
@@ -561,7 +561,8 @@ def test_principal_override(self):
561561
name=gssapi_name("user@REALM"))
562562
fake_init.assert_called_with(
563563
name=gssapi_name("[email protected]"),
564-
usage="initiate", flags=gssflags, creds=b"fake creds")
564+
usage="initiate", flags=gssflags,
565+
creds=b"fake creds", mech=None)
565566

566567
def test_realm_override(self):
567568
with patch.multiple("gssapi.SecurityContext", __init__=fake_init,
@@ -575,7 +576,7 @@ def test_realm_override(self):
575576
auth.generate_request_header(response, host)
576577
fake_init.assert_called_with(
577578
name=gssapi_name("[email protected]"),
578-
usage="initiate", flags=gssflags, creds=None)
579+
usage="initiate", flags=gssflags, creds=None, mech=None)
579580
fake_resp.assert_called_with(b"token")
580581

581582
def test_opportunistic_auth(self):
@@ -604,7 +605,25 @@ def test_explicit_creds(self):
604605
auth.generate_request_header(response, host)
605606
fake_init.assert_called_with(
606607
name=gssapi_name("[email protected]"),
607-
usage="initiate", flags=gssflags, creds=b"fake creds")
608+
usage="initiate", flags=gssflags,
609+
creds=b"fake creds", mech=None)
610+
fake_resp.assert_called_with(b"token")
611+
612+
def test_explicit_mech(self):
613+
with patch.multiple("gssapi.Credentials", __new__=fake_creds), \
614+
patch.multiple("gssapi.SecurityContext", __init__=fake_init,
615+
step=fake_resp):
616+
response = requests.Response()
617+
response.url = "http://www.example.org/"
618+
response.headers = {'www-authenticate': b64_negotiate_token}
619+
host = urlparse(response.url).hostname
620+
fake_mech = b'fake mech'
621+
auth = requests_gssapi.HTTPSPNEGOAuth(mech=fake_mech)
622+
auth.generate_request_header(response, host)
623+
fake_init.assert_called_with(
624+
name=gssapi_name("[email protected]"),
625+
usage="initiate", flags=gssflags,
626+
creds=None, mech=b'fake mech')
608627
fake_resp.assert_called_with(b"token")
609628

610629
def test_target_name(self):
@@ -619,7 +638,7 @@ def test_target_name(self):
619638
auth.generate_request_header(response, host)
620639
fake_init.assert_called_with(
621640
name=gssapi_name("[email protected]"),
622-
usage="initiate", flags=gssflags, creds=None)
641+
usage="initiate", flags=gssflags, creds=None, mech=None)
623642
fake_resp.assert_called_with(b"token")
624643

625644

0 commit comments

Comments
 (0)