diff --git a/HISTORY.rst b/HISTORY.rst index 53dc91c..49f7465 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -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 ----------------- diff --git a/README.rst b/README.rst index a79aceb..c77cc02 100644 --- a/README.rst +++ b/README.rst @@ -171,10 +171,11 @@ 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 @@ -182,10 +183,10 @@ without interference. It is expected to be an instance of ``gssapi.mechs.Mechani >>> 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) ... diff --git a/httpx_gssapi/__init__.py b/httpx_gssapi/__init__.py index 7e8a994..3c53224 100644 --- a/httpx_gssapi/__init__.py +++ b/httpx_gssapi/__init__.py @@ -16,6 +16,7 @@ __all__ = ( 'HTTPSPNEGOAuth', 'MutualAuthenticationError', + 'SPNEGO', 'REQUIRED', 'OPTIONAL', 'DISABLED', @@ -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 diff --git a/httpx_gssapi/gssapi_.py b/httpx_gssapi/gssapi_.py index cdd613b..fcee28a 100644 --- a/httpx_gssapi/gssapi_.py +++ b/httpx_gssapi/gssapi_.py @@ -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 @@ -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 @@ -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. @@ -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 diff --git a/tests/test_mocked.py b/tests/test_mocked.py index 798a5b6..770bfc3 100644 --- a/tests/test_mocked.py +++ b/tests/test_mocked.py @@ -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) @@ -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()