pax_global_header00006660000000000000000000000064140103362540014510gustar00rootroot0000000000000052 comment=492a4abbbaf8185dad4be09aae9476083ad1ef34 requests-gssapi-1.2.3/000077500000000000000000000000001401033625400146525ustar00rootroot00000000000000requests-gssapi-1.2.3/.copr/000077500000000000000000000000001401033625400156735ustar00rootroot00000000000000requests-gssapi-1.2.3/.copr/Makefile000066400000000000000000000003401401033625400173300ustar00rootroot00000000000000srpm: dnf install -y python2 sed "s/name='requests-gssapi'/name='python-requests-gssapi'/" setup.py > .copr/setup.py python .copr/setup.py bdist_rpm --source-only --dist-dir "$(outdir)" --build-requires=python-setuptools requests-gssapi-1.2.3/.github/000077500000000000000000000000001401033625400162125ustar00rootroot00000000000000requests-gssapi-1.2.3/.github/workflows/000077500000000000000000000000001401033625400202475ustar00rootroot00000000000000requests-gssapi-1.2.3/.github/workflows/build.yml000066400000000000000000000021301401033625400220650ustar00rootroot00000000000000{ "name": "Build", "on": { "pull_request": null }, "jobs": { "linux": { "runs-on": "ubuntu-latest", "strategy": { "fail-fast": false, "matrix": { "python": [ "3.6", "3.7", "3.8", "3.9", ], }, }, "steps": [ { "uses": "actions/checkout@v2" }, { "uses": "actions/setup-python@v2", "with": { "python-version": "${{ matrix.python }}"}, }, { "run": "sudo apt update" }, { "run": "sudo apt install -y libkrb5-dev" }, { "run": "pip install flake8" }, { "run": "pip install -r requirements.txt" }, { "run": "python3 -m unittest" }, { "run": "flake8", "if": "${{ matrix['python'] == 3.9 }}", }, ], }, }, } requests-gssapi-1.2.3/.gitignore000066400000000000000000000000711401033625400166400ustar00rootroot00000000000000*.pyc *.swp env/ build/ dist/ requests_gssapi.egg-info/ requests-gssapi-1.2.3/AUTHORS000066400000000000000000000001011401033625400157120ustar00rootroot00000000000000Michael Komitee Jose Castro Leon David Pursehouse Robbie Harwood requests-gssapi-1.2.3/HISTORY.rst000066400000000000000000000063231401033625400165510ustar00rootroot00000000000000History ======= 1.2.3: 2021-02-08 ----------------- - Drop python2 compat glue - Drop external mock dependency 1.2.2: 2020-08-07 ----------------- - Use USER_NAME instead of HOSTBASED_SERVICE for user principals - Remove unused imports in example code - Fix typo in explicit mech example 1.2.1: 2020-03-31 ----------------- - Include tests in sdist tarball - Don't limit contexts to a single server name 1.2.0: 2020-02-18 ----------------- - Add support for specifing an explicit GSSAPI mech 1.1.1: 2020-02-18 ----------------- - Fix DOS bug around Negotiate regular expressoin - Update README to include section on setup 1.1.0: 2019-05-21 ----------------- - Disable mutual authentication by default - Add more documentation on MutualAuthenticationError 1.0.1: 2019-04-10 ----------------- - Fix example in README - Fix license detection for PyPI - Fix a problem with regex escaping - Add COPR Makefile target 1.0.0: 2017-12-14 ----------------- - Fork project to requests-gssapi - Replace pykerberos with python-gssapi - Add HTTPSPNEGOAuth interface. HTTPKerberosAuth is retained as a shim, but bump the major version anyway for clarity. 0.11.0: 2016-11-02 ------------------ - Switch dependency on Windows from kerberos-sspi/pywin32 to WinKerberos. This brings Custom Principal support to Windows users. 0.10.0: 2016-05-18 ------------------ - Make it possible to receive errors without having their contents and headers stripped. - Resolve a bug caused by passing the ``principal`` keyword argument to kerberos-sspi on Windows. 0.9.0: 2016-05-06 ----------------- - Support for principal, hostname, and realm override. - Added support for mutual auth. 0.8.0: 2016-01-07 ----------------- - Support for Kerberos delegation. - Fixed problems declaring kerberos-sspi on Windows installs. 0.7.0: 2015-05-04 ----------------- - Added Windows native authentication support by adding kerberos-sspi as an alternative backend. - Prevent infinite recursion when a server returns 401 to an authorization attempt. - Reduce the logging during successful responses. 0.6.1: 2014-11-14 ----------------- - Fix HTTPKerberosAuth not to treat non-file as a file - Prevent infinite recursion when GSSErrors occurs 0.6: 2014-11-04 --------------- - Handle mutual authentication (see pull request 36_) All users should upgrade immediately. This has been reported to oss-security_ and we are awaiting a proper CVE identifier. **Update**: We were issued CVE-2014-8650 - Distribute as a wheel. .. _36: https://github.com/requests/requests-kerberos/pull/36 .. _oss-security: http://www.openwall.com/lists/oss-security/ 0.5: 2014-05-14 --------------- - Allow non-HTTP service principals with HTTPKerberosAuth using a new optional argument ``service``. - Fix bug in ``setup.py`` on distributions where the ``compiler`` module is not available. - Add test dependencies to ``setup.py`` so ``python setup.py test`` will work. 0.4: 2013-10-26 --------------- - Minor updates in the README - Change requirements to depend on requests above 1.1.0 0.3: 2013-06-02 --------------- - Work with servers operating on non-standard ports 0.2: 2013-03-26 --------------- - Not documented 0.1: Never released ------------------- - Initial Release requests-gssapi-1.2.3/LICENSE000066400000000000000000000015101401033625400156540ustar00rootroot00000000000000ISC License Copyright (c) 2012-2017 Kenneth Reitz Copyright (c) 2017 the python-requests-gssapi contributors Copyright (c) 2017 Red Hat, Inc. Permission to use, copy, modify and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. requests-gssapi-1.2.3/MANIFEST.in000066400000000000000000000002001401033625400164000ustar00rootroot00000000000000include requirements.txt include README.rst include LICENSE include HISTORY.rst include AUTHORS include test_requests_gssapi.py requests-gssapi-1.2.3/README.rst000066400000000000000000000211451401033625400163440ustar00rootroot00000000000000requests GSSAPI authentication library =============================================== Requests is an HTTP library, written in Python, for human beings. This library adds optional GSSAPI authentication support and supports mutual authentication. It provides a fully backward-compatible shim for the old python-requests-kerberos library: simply replace ``import requests_kerberos`` with ``import requests_gssapi``. A more powerful interface is provided by the HTTPSPNEGOAuth component, but this is of course not guaranteed to be compatible. Documentation below is written toward the new interface. Basic GET usage: .. code-block:: python >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth >>> r = requests.get("http://example.org", auth=HTTPSPNEGOAuth()) ... The entire ``requests.api`` should be supported. Setup ----- In order to use this library, there must already be a Kerberos Ticket-Granting Ticket (TGT) in a credential cache (ccache). Whether a TGT is available can be easily determined by running the ``klist`` command. If no TGT is available, then it first must be obtained (for instance, by running the ``kinit`` command, or pointing the $KRB5CCNAME to a credential cache with a valid TGT). In short, the library will handle the "negotiations" of Kerberos authentication, but ensuring that a credentials are available and valid is the responsibility of the user. Authentication Failures ----------------------- Client authentication failures will be communicated to the caller by returning a 401 response. A 401 response may also be the result of expired credentials (including the TGT). Mutual Authentication --------------------- Mutual authentication is a poorly-named feature of the GSSAPI which doesn't provide any additional security benefit to most possible uses of requests_gssapi. Practically speaking, in most mechanism implementations (including krb5), it requires another round-trip between the client and server during the authentication handshake. Many clients and servers do not properly handle the authentication handshake taking more than one round-trip. If you encounter a MutualAuthenticationError, this is probably why. So long as you're running over a TLS link whose security guarantees you trust, there's no benefit to mutual authentication. If you don't trust the link at all, mutual authentication won't help (since it's not tamper-proof, and GSSAPI isn't being used post-authentication. There's some middle ground between the two where it helps a small amount (e.g., passive adversary over encrypted-but-unverified channel), but for Negotiate (what we're doing here), it's not generally helpful. For a more technical explanation of what mutual authentication actually guarantees, I refer you to rfc2743 (GSSAPIv2), rfc4120 (krb5 in GSSAPI), rfc4178 (SPNEGO), and rfc4559 (HTTP Negotiate). DISABLED ^^^^^^^^ By default, there's no need to explicitly disable mutual authentication. However, for compatability with older versions of request_gssapi or requests_kerberos, you can explicitly request it not be attempted: .. code-block:: python >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth, DISABLED >>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=DISABLED) >>> r = requests.get("https://example.org", auth=gssapi_auth) ... REQUIRED ^^^^^^^^ This was historically the default, but no longer is. If requested, ``HTTPSPNEGOAuth`` will require mutual authentication from the server, and if a server emits a non-error response which cannot be authenticated, a ``requests_gssapi.errors.MutualAuthenticationError`` will be raised. (See above for what this means.) If a server emits an error which cannot be authenticated, it will be returned to the user but with its contents and headers stripped. If the response content is more important than the need for mutual auth on errors, (eg, for certain WinRM calls) the stripping behavior can be suppressed by setting ``sanitize_mutual_error_response=False``: .. code-block:: python >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth, REQUIRED >>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=REQUIRED, sanitize_mutual_error_response=False) >>> r = requests.get("https://windows.example.org/wsman", auth=gssapi_auth) ... OPTIONAL ^^^^^^^^ This will cause ``requests_gssapi`` to attempt mutual authentication if the server advertises that it supports it, and cause a failure if authentication fails, but not if the server does not support it at all. This is probably not what you want: link tampering will either cause hard failures, or silently cause it to not happen at all. It is retained for compatability. .. code-block:: python >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth, OPTIONAL >>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL) >>> r = requests.get("https://example.org", auth=gssapi_auth) ... Opportunistic Authentication ---------------------------- ``HTTPSPNEGOAuth`` can be forced to preemptively initiate the GSSAPI exchange and present a token on the initial request (and all subsequent). By default, authentication only occurs after a ``401 Unauthorized`` response containing a Negotiate challenge is received from the origin server. This can cause mutual authentication failures for hosts that use a persistent connection (eg, Windows/WinRM), as no GSSAPI challenges are sent after the initial auth handshake. This behavior can be altered by setting ``opportunistic_auth=True``: .. code-block:: python >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth >>> gssapi_auth = HTTPSPNEGOAuth(opportunistic_auth=True) >>> r = requests.get("https://windows.example.org/wsman", auth=gssapi_auth) ... Hostname Override ----------------- If communicating with a host whose DNS name doesn't match its hostname (eg, behind a content switch or load balancer), the hostname used for the GSSAPI exchange can be overridden by passing in a custom name (string or ``gssapi.Name``): .. code-block:: python >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth >>> gssapi_auth = HTTPSPNEGOAuth(target_name="internalhost.local") >>> r = requests.get("https://externalhost.example.org/", auth=gssapi_auth) ... Explicit Principal ------------------ ``HTTPSPNEGOAuth`` normally uses the default principal (ie, the user for whom you last ran ``kinit`` or ``kswitch``, or an SSO credential if applicable). However, an explicit credential can be in instead, if desired. .. code-block:: python >>> import gssapi >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth >>> name = gssapi.Name("user@REALM", gssapi.NameType.hostbased_service) >>> creds = gssapi.Credentials(name=name, usage="initiate") >>> gssapi_auth = HTTPSPNEGOAuth(creds=creds) >>> 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 ---------- ``requests_gssapi`` supports credential delegation (``GSS_C_DELEG_FLAG``). To enable delegation of credentials to a server that requests delegation, pass ``delegate=True`` to ``HTTPSPNEGOAuth``: .. code-block:: python >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth >>> r = requests.get("http://example.org", auth=HTTPSPNEGOAuth(delegate=True)) ... Be careful to only allow delegation to servers you trust as they will be able to impersonate you using the delegated credentials. Logging ------- This library makes extensive use of Python's logging facilities. Log messages are logged to the ``requests_gssapi`` and ``requests_gssapi.gssapi`` named loggers. If you are having difficulty we suggest you configure logging. Issues with the underlying GSSAPI libraries will be made apparent. Additionally, copious debug information is made available which may assist in troubleshooting if you increase your log level all the way up to debug. requests-gssapi-1.2.3/requests_gssapi/000077500000000000000000000000001401033625400200735ustar00rootroot00000000000000requests-gssapi-1.2.3/requests_gssapi/__init__.py000066400000000000000000000015331401033625400222060ustar00rootroot00000000000000""" requests GSSAPI authentication library ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Requests is an HTTP library, written in Python, for human beings. This library adds optional GSSAPI authentication support and supports mutual authentication. Basic GET usage: >>> import requests >>> from requests_gssapi import HTTPSPNEGOAuth >>> r = requests.get("http://example.org", auth=HTTPSPNEGOAuth()) The entire `requests.api` should be supported. """ import logging from .gssapi_ import HTTPSPNEGOAuth, REQUIRED, OPTIONAL, DISABLED # noqa from .exceptions import MutualAuthenticationError from .compat import NullHandler, HTTPKerberosAuth logging.getLogger(__name__).addHandler(NullHandler()) __all__ = ('HTTPSPNEGOAuth', 'HTTPKerberosAuth', 'MutualAuthenticationError', 'REQUIRED', 'OPTIONAL', 'DISABLED') __version__ = '1.2.3' requests-gssapi-1.2.3/requests_gssapi/compat.py000066400000000000000000000056521401033625400217400ustar00rootroot00000000000000""" Compatibility library for older versions of python and requests_kerberos """ import sys import gssapi from .gssapi_ import DISABLED, HTTPSPNEGOAuth, SPNEGOExchangeError, log # python 2.7 introduced a NullHandler which we want to use, but to support # older versions, we implement our own if needed. if sys.version_info[:2] > (2, 6): from logging import NullHandler else: from logging import Handler class NullHandler(Handler): def emit(self, record): pass class HTTPKerberosAuth(HTTPSPNEGOAuth): """Deprecated compat shim; see HTTPSPNEGOAuth instead.""" def __init__(self, mutual_authentication=DISABLED, service="HTTP", delegate=False, force_preemptive=False, principal=None, hostname_override=None, sanitize_mutual_error_response=True): # put these here for later self.principal = principal self.service = service self.hostname_override = hostname_override HTTPSPNEGOAuth.__init__( self, mutual_authentication=mutual_authentication, target_name=None, delegate=delegate, opportunistic_auth=force_preemptive, creds=None, sanitize_mutual_error_response=sanitize_mutual_error_response) def generate_request_header(self, response, host, is_preemptive=False): # This method needs to be shimmed because `host` isn't exposed to # __init__() and we need to derive things from it. Also, __init__() # can't fail, in the strictest compatability sense. try: if self.principal is not None: gss_stage = "acquiring credentials" name = gssapi.Name( self.principal, gssapi.NameType.user) self.creds = gssapi.Credentials(name=name, usage="initiate") # contexts still need to be stored by host, but hostname_override # allows use of an arbitrary hostname for the GSSAPI exchange (eg, # in cases of aliased hosts, internal vs external, CNAMEs w/ # name-based HTTP hosting) if self.service is not None: gss_stage = "initiating context" kerb_host = host if self.hostname_override: kerb_host = self.hostname_override kerb_spn = "{0}@{1}".format(self.service, kerb_host) self.target_name = gssapi.Name( kerb_spn, gssapi.NameType.hostbased_service) return HTTPSPNEGOAuth.generate_request_header(self, response, host, is_preemptive) except gssapi.exceptions.GSSError as error: msg = error.gen_message() log.exception( "generate_request_header(): {0} failed:".format(gss_stage)) log.exception(msg) raise SPNEGOExchangeError("%s failed: %s" % (gss_stage, msg)) requests-gssapi-1.2.3/requests_gssapi/exceptions.py000066400000000000000000000006331401033625400226300ustar00rootroot00000000000000""" requests_gssapi.exceptions ~~~~~~~~~~~~~~~~~~~ This module contains the set of exceptions. """ from requests.exceptions import RequestException class MutualAuthenticationError(RequestException): """Mutual Authentication Error""" class SPNEGOExchangeError(RequestException): """SPNEGO Exchange Failed Error""" """ Deprecated compatability shim """ KerberosExchangeError = SPNEGOExchangeError requests-gssapi-1.2.3/requests_gssapi/gssapi_.py000066400000000000000000000310151401033625400220720ustar00rootroot00000000000000import re import logging from base64 import b64encode, b64decode import gssapi from requests.auth import AuthBase from requests.models import Response from requests.compat import urlparse from requests.structures import CaseInsensitiveDict from requests.cookies import cookiejar_from_dict from .exceptions import MutualAuthenticationError, SPNEGOExchangeError log = logging.getLogger(__name__) # Different types of mutual authentication: # with mutual_authentication set to REQUIRED, all responses will be # authenticated with the exception of errors. Errors will have their contents # and headers stripped. If a non-error response cannot be authenticated, a # MutualAuthenticationError exception will be raised. # with mutual_authentication set to OPTIONAL, mutual authentication will be # attempted if supported, and if supported and failed, a # MutualAuthenticationError exception will be raised. Responses which do not # support mutual authentication will be returned directly to the user. # with mutual_authentication set to DISABLED, mutual authentication will not be # attempted, even if supported. REQUIRED = 1 OPTIONAL = 2 DISABLED = 3 class SanitizedResponse(Response): """The :class:`Response ` object, which contains a server's response to an HTTP request. This differs from `requests.models.Response` in that it's headers and content have been sanitized. This is only used for HTTP Error messages which do not support mutual authentication when mutual authentication is required.""" def __init__(self, response): super(SanitizedResponse, self).__init__() self.status_code = response.status_code self.encoding = response.encoding self.raw = response.raw self.reason = response.reason self.url = response.url self.request = response.request self.connection = response.connection self._content_consumed = True self._content = "" self.cookies = cookiejar_from_dict({}) self.headers = CaseInsensitiveDict() self.headers['content-length'] = '0' for header in ('date', 'server'): if header in response.headers: self.headers[header] = response.headers[header] def _negotiate_value(response): """Extracts the gssapi authentication token from the appropriate header""" if hasattr(_negotiate_value, 'regex'): regex = _negotiate_value.regex else: # There's no need to re-compile this EVERY time it is called. Compile # it once and you won't have the performance hit of the compilation. regex = re.compile(r'Negotiate\s*([^,]*)', re.I) _negotiate_value.regex = regex authreq = response.headers.get('www-authenticate', None) if authreq: match_obj = regex.search(authreq) if match_obj: return b64decode(match_obj.group(1)) return None class HTTPSPNEGOAuth(AuthBase): """Attaches HTTP GSSAPI Authentication to the given Request object. `mutual_authentication` controls whether GSSAPI should attempt mutual authentication. It may be `REQUIRED`, `OPTIONAL`, or `DISABLED` (default). `target_name` specifies the remote principal name. It may be either a GSSAPI Name type or a string. If a string, it will be treated as a hostbased service; if it contains no '@', the DNS host will be supplied. (default: "HTTP" at the DNS host). `delegate` indicates whether we should attempt credential delegation. Default is `False`. `opportunistic_auth` indicates whether we should assume the server will ask for Negotiation. Defaut is `False`. `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, 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): """ Generates the GSSAPI authentication token If any GSSAPI step fails, raise SPNEGOExchangeError with failure detail. """ gssflags = [gssapi.RequirementFlag.out_of_sequence_detection] if self.delegate: gssflags.append(gssapi.RequirementFlag.delegate_to_peer) if self.mutual_authentication != DISABLED: gssflags.append(gssapi.RequirementFlag.mutual_authentication) try: gss_stage = "initiating context" name = self.target_name if type(name) != gssapi.Name: if '@' not in name: name = "%s@%s" % (name, host) name = gssapi.Name(name, gssapi.NameType.hostbased_service) self.context[host] = gssapi.SecurityContext( usage="initiate", flags=gssflags, name=name, creds=self.creds, mech=self.mech) gss_stage = "stepping context" if is_preemptive: gss_response = self.context[host].step() else: gss_response = self.context[host].step( _negotiate_value(response)) return "Negotiate {0}".format(b64encode(gss_response).decode()) except gssapi.exceptions.GSSError as error: msg = error.gen_message() log.exception( "generate_request_header(): {0} failed:".format(gss_stage)) log.exception(msg) raise SPNEGOExchangeError("%s failed: %s" % (gss_stage, msg)) def authenticate_user(self, response, **kwargs): """Handles user authentication with GSSAPI""" host = urlparse(response.url).hostname try: auth_header = self.generate_request_header(response, host) except SPNEGOExchangeError: # GSS Failure, return existing response return response log.debug("authenticate_user(): Authorization header: {0}".format( auth_header)) response.request.headers['Authorization'] = auth_header # Consume the content so we can reuse the connection for the next # request. response.content response.raw.release_conn() _r = response.connection.send(response.request, **kwargs) _r.history.append(response) log.debug("authenticate_user(): returning {0}".format(_r)) return _r def handle_401(self, response, **kwargs): """Handles 401's, attempts to use GSSAPI authentication""" log.debug("handle_401(): Handling: 401") if _negotiate_value(response) is not None: _r = self.authenticate_user(response, **kwargs) log.debug("handle_401(): returning {0}".format(_r)) return _r else: log.debug("handle_401(): GSSAPI is not supported") log.debug("handle_401(): returning {0}".format(response)) return response def handle_other(self, response): """Handles all responses with the exception of 401s. This is necessary so that we can authenticate responses if requested""" log.debug("handle_other(): Handling: %d" % response.status_code) if self.mutual_authentication not in (REQUIRED, OPTIONAL): log.debug("handle_other(): returning {0}".format(response)) return response is_http_error = response.status_code >= 400 if _negotiate_value(response) is not None: log.debug("handle_other(): Authenticating the server") if not self.authenticate_server(response): # Mutual authentication failure when mutual auth is wanted, # raise an exception so the user doesn't use an untrusted # response. log.error("handle_other(): Mutual authentication failed") raise MutualAuthenticationError( "Unable to authenticate {0}".format(response)) # Authentication successful log.debug("handle_other(): returning {0}".format(response)) return response elif is_http_error or self.mutual_authentication == OPTIONAL: if not response.ok: log.error( "handle_other(): Mutual authentication unavailable on" " {0} response".format(response.status_code)) if self.mutual_authentication == REQUIRED and \ self.sanitize_mutual_error_response: return SanitizedResponse(response) return response else: # Unable to attempt mutual authentication when mutual auth is # required, raise an exception so the user doesn't use an # untrusted response. log.error("handle_other(): Mutual authentication failed") raise MutualAuthenticationError( "Unable to authenticate {0}".format(response)) def authenticate_server(self, response): """ Uses GSSAPI to authenticate the server. Returns True on success, False on failure. """ log.debug("authenticate_server(): Authenticate header: {0}".format( _negotiate_value(response))) host = urlparse(response.url).hostname try: # If the handshake isn't complete here, nothing we can do self.context[host].step(_negotiate_value(response)) except gssapi.exceptions.GSSError as error: log.exception("authenticate_server(): context stepping failed:") log.exception(error.gen_message()) return False log.debug("authenticate_server(): returning {0}".format(response)) return True def handle_response(self, response, **kwargs): """Takes the given response and tries GSSAPI auth, as needed.""" num_401s = kwargs.pop('num_401s', 0) if self.pos is not None: # Rewind the file position indicator of the body to where # it was to resend the request. response.request.body.seek(self.pos) if response.status_code == 401 and num_401s < 2: # 401 Unauthorized. Handle it, and if it still comes back as 401, # that means authentication failed. _r = self.handle_401(response, **kwargs) log.debug("handle_response(): returning %s", _r) log.debug("handle_response() has seen %d 401 responses", num_401s) num_401s += 1 return self.handle_response(_r, num_401s=num_401s, **kwargs) elif response.status_code == 401 and num_401s >= 2: # Still receiving 401 responses after attempting to handle them. # Authentication has failed. Return the 401 response. log.debug("handle_response(): returning 401 %s", response) return response _r = self.handle_other(response) log.debug("handle_response(): returning %s", _r) return _r def deregister(self, response): """Deregisters the response handler""" response.request.deregister_hook('response', self.handle_response) def __call__(self, request): if self.opportunistic_auth: # add Authorization header before we receive a 401 # by the 401 handler host = urlparse(request.url).hostname auth_header = self.generate_request_header(None, host, is_preemptive=True) log.debug( "HTTPSPNEGOAuth: Preemptive Authorization header: {0}" .format(auth_header)) request.headers['Authorization'] = auth_header request.register_hook('response', self.handle_response) try: self.pos = request.body.tell() except AttributeError: # In the case of HTTPSPNEGOAuth being reused and the body # of the previous request was a file-like object, pos has # the file position of the previous body. Ensure it's set to # None. self.pos = None return request requests-gssapi-1.2.3/requirements.txt000066400000000000000000000000271401033625400201350ustar00rootroot00000000000000requests>=1.1.0 gssapi requests-gssapi-1.2.3/setup.cfg000066400000000000000000000000261401033625400164710ustar00rootroot00000000000000[wheel] universal = 1 requests-gssapi-1.2.3/setup.py000077500000000000000000000030301401033625400163630ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 import os import re from setuptools import setup path = os.path.dirname(__file__) desc_fd = os.path.join(path, 'README.rst') hist_fd = os.path.join(path, 'HISTORY.rst') long_desc = '' short_desc = 'A GSSAPI authentication handler for python-requests' if os.path.isfile(desc_fd): with open(desc_fd) as fd: long_desc = fd.read() if os.path.isfile(hist_fd): with open(hist_fd) as fd: long_desc = '\n\n'.join([long_desc, fd.read()]) def get_version(): """ Simple function to extract the current version using regular expressions. """ reg = re.compile(r'__version__ = [\'"]([^\'"]*)[\'"]') with open('requests_gssapi/__init__.py') as fd: matches = list(filter(lambda x: x, map(reg.match, fd))) if not matches: raise RuntimeError( 'Could not find the version information for requests_gssapi' ) return matches[0].group(1) setup( name='requests-gssapi', description=short_desc, long_description=long_desc, author='Ian Cordasco, Cory Benfield, Michael Komitee, Robbie Harwood', author_email='rharwood@redhat.com', url='https://github.com/pythongssapi/requests-gssapi', packages=['requests_gssapi'], package_data={'': ['LICENSE', 'AUTHORS']}, include_package_data=True, version=get_version(), install_requires=[ 'requests>=1.1.0', 'gssapi', ], test_suite='test_requests_gssapi', classifiers=[ "License :: OSI Approved :: ISC License (ISCL)" ], ) requests-gssapi-1.2.3/test_requests_gssapi.py000066400000000000000000000664231401033625400215170ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Tests for requests_gssapi.""" from base64 import b64encode from unittest.mock import Mock, patch from requests.compat import urlparse import requests import gssapi import requests_gssapi import unittest from requests_gssapi import REQUIRED # Note: we're not using the @mock.patch decorator: # > My only word of warning is that in the past, the patch decorator hides # > tests when using the standard unittest library. # > -- sigmavirus24 in https://github.com/requests/requests-kerberos/issues/1 fake_init = Mock(return_value=None) fake_creds = Mock(return_value=b"fake creds") fake_resp = Mock(return_value=b"GSSRESPONSE") # GSSAPI exceptions require a major and minor status code for their # construction, so construct a *really* fake one fail_resp = Mock(side_effect=gssapi.exceptions.GSSError(0, 0)) gssflags = [gssapi.RequirementFlag.out_of_sequence_detection] mutflags = gssflags + [gssapi.RequirementFlag.mutual_authentication] gssdelegflags = gssflags + [gssapi.RequirementFlag.delegate_to_peer] # The base64 behavior we want is that encoding produces a string, but decoding # produces bytes. Remember, GSSAPI tokens are opaque here. b64_negotiate_response = "Negotiate " + b64encode(b"GSSRESPONSE").decode() b64_negotiate_token = "negotiate " + b64encode(b"token").decode() b64_negotiate_server = "negotiate " + b64encode(b"servertoken").decode() def gssapi_sname(s): return gssapi.Name(s, gssapi.NameType.hostbased_service) def gssapi_uname(s): return gssapi.Name(s, gssapi.NameType.user) class GSSAPITestCase(unittest.TestCase): def setUp(self): """Setup.""" fake_init.reset_mock() fake_resp.reset_mock() fail_resp.reset_mock() fake_creds.reset_mock() def tearDown(self): """Teardown.""" pass def test_negotate_value_extraction(self): response = requests.Response() response.headers = {'www-authenticate': b64_negotiate_token} self.assertEqual( requests_gssapi.gssapi_._negotiate_value(response), b'token' ) def test_negotate_value_extraction_none(self): response = requests.Response() response.headers = {} self.assertTrue( requests_gssapi.gssapi_._negotiate_value(response) is None) def test_force_preemptive(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): auth = requests_gssapi.HTTPKerberosAuth(force_preemptive=True) request = requests.Request(url="http://www.example.org") auth.__call__(request) self.assertTrue('Authorization' in request.headers) self.assertEqual(request.headers.get('Authorization'), b64_negotiate_response) def test_no_force_preemptive(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): auth = requests_gssapi.HTTPKerberosAuth() request = requests.Request(url="http://www.example.org") auth.__call__(request) self.assertTrue('Authorization' not in request.headers) def test_generate_request_header(self): with 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 auth = requests_gssapi.HTTPKerberosAuth() self.assertEqual( auth.generate_request_header(response, host), b64_negotiate_response) fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), creds=None, mech=None, flags=gssflags, usage="initiate") fake_resp.assert_called_with(b"token") def test_generate_request_header_init_error(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fail_resp): response = requests.Response() response.url = "http://www.example.org/" response.headers = {'www-authenticate': b64_negotiate_token} host = urlparse(response.url).hostname auth = requests_gssapi.HTTPKerberosAuth() self.assertRaises(requests_gssapi.exceptions.SPNEGOExchangeError, auth.generate_request_header, response, host) fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), usage="initiate", flags=gssflags, creds=None, mech=None) def test_generate_request_header_step_error(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fail_resp): response = requests.Response() response.url = "http://www.example.org/" response.headers = {'www-authenticate': b64_negotiate_token} host = urlparse(response.url).hostname auth = requests_gssapi.HTTPKerberosAuth() self.assertRaises(requests_gssapi.exceptions.SPNEGOExchangeError, auth.generate_request_header, response, host) fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), usage="initiate", flags=gssflags, creds=None, mech=None) fail_resp.assert_called_with(b"token") def test_authenticate_user(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = {'www-authenticate': b64_negotiate_server} connection = Mock() connection.send = Mock(return_value=response_ok) raw = Mock() raw.release_conn = Mock(return_value=None) request = requests.Request() response = requests.Response() response.request = request response.url = "http://www.example.org/" response.headers = {'www-authenticate': b64_negotiate_token} response.status_code = 401 response.connection = connection response._content = "" response.raw = raw auth = requests_gssapi.HTTPKerberosAuth() r = auth.authenticate_user(response) self.assertTrue(response in r.history) self.assertEqual(r, response_ok) self.assertEqual(request.headers['Authorization'], b64_negotiate_response) connection.send.assert_called_with(request) raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), flags=gssflags, usage="initiate", creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_handle_401(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = {'www-authenticate': b64_negotiate_server} connection = Mock() connection.send = Mock(return_value=response_ok) raw = Mock() raw.release_conn = Mock(return_value=None) request = requests.Request() response = requests.Response() response.request = request response.url = "http://www.example.org/" response.headers = {'www-authenticate': b64_negotiate_token} response.status_code = 401 response.connection = connection response._content = "" response.raw = raw auth = requests_gssapi.HTTPKerberosAuth() r = auth.handle_401(response) self.assertTrue(response in r.history) self.assertEqual(r, response_ok) self.assertEqual(request.headers['Authorization'], b64_negotiate_response) connection.send.assert_called_with(request) raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), creds=None, mech=None, flags=gssflags, usage="initiate") fake_resp.assert_called_with(b"token") def test_authenticate_server(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = { 'www-authenticate': b64_negotiate_server, 'authorization': b64_negotiate_response} auth = requests_gssapi.HTTPKerberosAuth() auth.context = {"www.example.org": gssapi.SecurityContext} result = auth.authenticate_server(response_ok) self.assertTrue(result) fake_resp.assert_called_with(b"servertoken") def test_handle_other(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = { 'www-authenticate': b64_negotiate_server, 'authorization': b64_negotiate_response} auth = requests_gssapi.HTTPKerberosAuth( mutual_authentication=REQUIRED) auth.context = {"www.example.org": gssapi.SecurityContext} r = auth.handle_other(response_ok) self.assertEqual(r, response_ok) fake_resp.assert_called_with(b"servertoken") def test_handle_response_200(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = { 'www-authenticate': b64_negotiate_server, 'authorization': b64_negotiate_response} auth = requests_gssapi.HTTPKerberosAuth( mutual_authentication=REQUIRED) auth.context = {"www.example.org": gssapi.SecurityContext} r = auth.handle_response(response_ok) self.assertEqual(r, response_ok) fake_resp.assert_called_with(b"servertoken") def test_handle_response_200_mutual_auth_required_failure(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = {} auth = requests_gssapi.HTTPKerberosAuth( mutual_authentication=REQUIRED) auth.context = {"www.example.org": "CTX"} self.assertRaises(requests_gssapi.MutualAuthenticationError, auth.handle_response, response_ok) self.assertFalse(fake_resp.called) def test_handle_response_200_mutual_auth_required_failure_2(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fail_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = { 'www-authenticate': b64_negotiate_server, 'authorization': b64_negotiate_response} auth = requests_gssapi.HTTPKerberosAuth( mutual_authentication=REQUIRED) auth.context = {"www.example.org": gssapi.SecurityContext} self.assertRaises(requests_gssapi.MutualAuthenticationError, auth.handle_response, response_ok) fail_resp.assert_called_with(b"servertoken") def test_handle_response_200_mutual_auth_optional_hard_failure(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fail_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = { 'www-authenticate': b64_negotiate_server, 'authorization': b64_negotiate_response} auth = requests_gssapi.HTTPKerberosAuth( requests_gssapi.OPTIONAL) auth.context = {"www.example.org": gssapi.SecurityContext} self.assertRaises(requests_gssapi.MutualAuthenticationError, auth.handle_response, response_ok) fail_resp.assert_called_with(b"servertoken") def test_handle_response_200_mutual_auth_optional_soft_failure(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 auth = requests_gssapi.HTTPKerberosAuth( requests_gssapi.OPTIONAL) auth.context = {"www.example.org": gssapi.SecurityContext} r = auth.handle_response(response_ok) self.assertEqual(r, response_ok) self.assertFalse(fake_resp.called) def test_handle_response_500_mutual_auth_required_failure(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fail_resp): response_500 = requests.Response() response_500.url = "http://www.example.org/" response_500.status_code = 500 response_500.headers = {} response_500.request = "REQUEST" response_500.connection = "CONNECTION" response_500._content = "CONTENT" response_500.encoding = "ENCODING" response_500.raw = "RAW" response_500.cookies = "COOKIES" auth = requests_gssapi.HTTPKerberosAuth( mutual_authentication=REQUIRED) auth.context = {"www.example.org": "CTX"} r = auth.handle_response(response_500) self.assertTrue( isinstance(r, requests_gssapi.gssapi_.SanitizedResponse)) self.assertNotEqual(r, response_500) self.assertNotEqual(r.headers, response_500.headers) self.assertEqual(r.status_code, response_500.status_code) self.assertEqual(r.encoding, response_500.encoding) self.assertEqual(r.raw, response_500.raw) self.assertEqual(r.url, response_500.url) self.assertEqual(r.reason, response_500.reason) self.assertEqual(r.connection, response_500.connection) self.assertEqual(r.content, '') self.assertNotEqual(r.cookies, response_500.cookies) self.assertFalse(fail_resp.called) # re-test with error response sanitizing disabled auth = requests_gssapi.HTTPKerberosAuth( sanitize_mutual_error_response=False) auth.context = {"www.example.org": "CTX"} r = auth.handle_response(response_500) self.assertFalse( isinstance(r, requests_gssapi.gssapi_.SanitizedResponse)) def test_handle_response_500_mutual_auth_optional_failure(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fail_resp): response_500 = requests.Response() response_500.url = "http://www.example.org/" response_500.status_code = 500 response_500.headers = {} response_500.request = "REQUEST" response_500.connection = "CONNECTION" response_500._content = "CONTENT" response_500.encoding = "ENCODING" response_500.raw = "RAW" response_500.cookies = "COOKIES" auth = requests_gssapi.HTTPKerberosAuth( requests_gssapi.OPTIONAL) auth.context = {"www.example.org": "CTX"} r = auth.handle_response(response_500) self.assertEqual(r, response_500) self.assertFalse(fail_resp.called) def test_handle_response_401(self): # Get a 401 from server, authenticate, and get a 200 back. with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = {'www-authenticate': b64_negotiate_server} connection = Mock() connection.send = Mock(return_value=response_ok) raw = Mock() raw.release_conn = Mock(return_value=None) request = requests.Request() response = requests.Response() response.request = request response.url = "http://www.example.org/" response.headers = {'www-authenticate': b64_negotiate_token} response.status_code = 401 response.connection = connection response._content = "" response.raw = raw auth = requests_gssapi.HTTPKerberosAuth() auth.handle_other = Mock(return_value=response_ok) r = auth.handle_response(response) self.assertTrue(response in r.history) auth.handle_other.assert_called_once_with(response_ok) self.assertEqual(r, response_ok) self.assertEqual(request.headers['Authorization'], b64_negotiate_response) connection.send.assert_called_with(request) raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_handle_response_401_rejected(self): # Get a 401 from server, authenticate, and get another 401 back. # Ensure there is no infinite recursion. with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): connection = Mock() def connection_send(self, *args, **kwargs): reject = requests.Response() reject.url = "http://www.example.org/" reject.status_code = 401 reject.connection = connection return reject connection.send.side_effect = connection_send raw = Mock() raw.release_conn.return_value = None request = requests.Request() response = requests.Response() response.request = request response.url = "http://www.example.org/" response.headers = {'www-authenticate': b64_negotiate_token} response.status_code = 401 response.connection = connection response._content = "" response.raw = raw auth = requests_gssapi.HTTPKerberosAuth() r = auth.handle_response(response) self.assertEqual(r.status_code, 401) self.assertEqual(request.headers['Authorization'], b64_negotiate_response) connection.send.assert_called_with(request) raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_generate_request_header_custom_service(self): with 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 auth = requests_gssapi.HTTPKerberosAuth(service="barfoo") auth.generate_request_header(response, host), fake_init.assert_called_with( name=gssapi_sname("barfoo@www.example.org"), usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_delegation(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): response_ok = requests.Response() response_ok.url = "http://www.example.org/" response_ok.status_code = 200 response_ok.headers = {'www-authenticate': b64_negotiate_server} connection = Mock() connection.send = Mock(return_value=response_ok) raw = Mock() raw.release_conn = Mock(return_value=None) request = requests.Request() response = requests.Response() response.request = request response.url = "http://www.example.org/" response.headers = {'www-authenticate': b64_negotiate_token} response.status_code = 401 response.connection = connection response._content = "" response.raw = raw auth = requests_gssapi.HTTPKerberosAuth(service="HTTP", delegate=True) r = auth.authenticate_user(response) self.assertTrue(response in r.history) self.assertEqual(r, response_ok) self.assertEqual(request.headers['Authorization'], b64_negotiate_response) connection.send.assert_called_with(request) raw.release_conn.assert_called_with() fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), usage="initiate", flags=gssdelegflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_principal_override(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 auth = requests_gssapi.HTTPKerberosAuth(principal="user@REALM") auth.generate_request_header(response, host) fake_creds.assert_called_with(gssapi.creds.Credentials, usage="initiate", name=gssapi_uname("user@REALM", )) fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), usage="initiate", flags=gssflags, creds=b"fake creds", mech=None) def test_realm_override(self): with 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 auth = requests_gssapi.HTTPKerberosAuth( hostname_override="otherhost.otherdomain.org") auth.generate_request_header(response, host) fake_init.assert_called_with( name=gssapi_sname("HTTP@otherhost.otherdomain.org"), usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") def test_opportunistic_auth(self): with patch.multiple("gssapi.SecurityContext", __init__=fake_init, step=fake_resp): auth = requests_gssapi.HTTPSPNEGOAuth(opportunistic_auth=True) request = requests.Request(url="http://www.example.org") auth.__call__(request) self.assertTrue('Authorization' in request.headers) self.assertEqual(request.headers.get('Authorization'), b64_negotiate_response) def test_explicit_creds(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 creds = gssapi.Credentials() auth = requests_gssapi.HTTPSPNEGOAuth(creds=creds) auth.generate_request_header(response, host) fake_init.assert_called_with( name=gssapi_sname("HTTP@www.example.org"), 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_sname("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): with 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 auth = requests_gssapi.HTTPSPNEGOAuth( target_name="HTTP@otherhost.otherdomain.org") auth.generate_request_header(response, host) fake_init.assert_called_with( name=gssapi_sname("HTTP@otherhost.otherdomain.org"), usage="initiate", flags=gssflags, creds=None, mech=None) fake_resp.assert_called_with(b"token") if __name__ == '__main__': unittest.main()