Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ History
FUTURE: TBD
-----------
- Update HISTORY.rst to reflect previous releases made on GitHub
- Use SPNEGO mechanism by default

0.4.0: 2024-11-28
-----------------
Expand Down
15 changes: 8 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,22 @@ applicable). However, an explicit credential can be in instead, if desired.
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``.
``HTTPSPNEGOAuth`` normally lets SPNEGO 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.Mechanism``, ``gssapi.OID``, or
raw bytes.

.. code-block:: python

>>> import gssapi
>>> import httpx
>>> from httpx_gssapi import HTTPSPNEGOAuth
>>> try:
... spnego = gssapi,mechs.Mechanism.from_sasl_name("SPNEGO")
... krb5 = gssapi.mechs.Mechanism.from_sasl_name("GS2-KRB5")
... except AttributeError:
... spnego = gssapi.OID.from_int_seq("1.3.6.1.5.5.2")
>>> gssapi_auth = HTTPSPNEGOAuth(mech=spnego)
... krb5 = gssapi.OID.from_int_seq("1.2.840.113554.1.2.2")
>>> gssapi_auth = HTTPSPNEGOAuth(mech=krb5)
>>> r = httpx.get("http://example.org", auth=gssapi_auth)
...

Expand Down
3 changes: 2 additions & 1 deletion httpx_gssapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
__all__ = (
'HTTPSPNEGOAuth',
'MutualAuthenticationError',
'SPNEGO',
'REQUIRED',
'OPTIONAL',
'DISABLED',
Expand All @@ -24,7 +25,7 @@
import os
import logging

from .gssapi_ import HTTPSPNEGOAuth, REQUIRED, OPTIONAL, DISABLED
from .gssapi_ import HTTPSPNEGOAuth, SPNEGO, REQUIRED, OPTIONAL, DISABLED
from .exceptions import MutualAuthenticationError

from ._version import get_versions
Expand Down
11 changes: 8 additions & 3 deletions httpx_gssapi/gssapi_.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
from itertools import chain
from functools import wraps
from typing import Generator, Optional, List, Any
from typing import Generator, Optional, List, Any, Union

from base64 import b64encode, b64decode

Expand Down Expand Up @@ -33,6 +33,9 @@
OPTIONAL = 2
DISABLED = 3

# OID for the SPNEGO mechanism
SPNEGO = gssapi.OID.from_int_seq("1.3.6.1.5.5.2")

_find_auth = re.compile(r'Negotiate\s*([^,]*)', re.I).search


Expand Down Expand Up @@ -125,7 +128,9 @@ class HTTPSPNEGOAuth(Auth):
Default is `None`.

`mech` is GSSAPI Mechanism (gssapi.Mechanism) to use for negotiation.
Default is `None`
If explicitly given as ``None``, the underlying ``gssapi`` library will
decide which negotiation mechanism to use.
Default is `SPNEGO`.

`sanitize_mutual_error_response` controls whether we should clean up
server responses. See the `SanitizedResponse` class.
Expand All @@ -138,7 +143,7 @@ def __init__(self,
delegate: bool = False,
opportunistic_auth: bool = False,
creds: gssapi.Credentials = None,
mech: bytes = None,
mech: Optional[Union[bytes, gssapi.OID]] = SPNEGO,
sanitize_mutual_error_response: bool = True):
self.mutual_authentication = mutual_authentication
self.target_name = target_name
Expand Down
11 changes: 10 additions & 1 deletion tests/test_mocked.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def null_response(status=200, request=null_request(), **kwargs):
def check_init(**kwargs):
kwargs.setdefault('name', gssapi_name("HTTP@www.example.org"))
kwargs.setdefault('creds', None)
kwargs.setdefault('mech', None)
kwargs.setdefault('mech', gssapi.OID.from_int_seq("1.3.6.1.5.5.2"))
kwargs.setdefault('flags', gssflags)
kwargs.setdefault('usage', "initiate")
fake_init.assert_called_with(**kwargs)
Expand Down Expand Up @@ -397,5 +397,14 @@ def test_target_name(patched_ctx):
fake_resp.assert_called_with(b"token")


def test_os_default_mech(patched_ctx):
resp = null_response(headers=neg_token)
auth = httpx_gssapi.HTTPSPNEGOAuth(mech=None)
auth.set_auth_header(resp.request, resp)
assert resp.request.headers['Authorization'] == b64_negotiate_response
check_init(mech=None)
fake_resp.assert_called_with(b"token")


if __name__ == '__main__':
pytest.main()