Pārlūkot izejas kodu

[libs] Adding requests-gssapi module for out of the box Phoenix connector (#1299)

Romain 5 gadi atpakaļ
vecāks
revīzija
ac051f3d2f

+ 4 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/AUTHORS

@@ -0,0 +1,4 @@
+Michael Komitee
+Jose Castro Leon
+David Pursehouse
+Robbie Harwood

+ 141 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/HISTORY.rst

@@ -0,0 +1,141 @@
+History
+=======
+
+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

+ 17 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/LICENSE

@@ -0,0 +1,17 @@
+ISC 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.

+ 6 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/MANIFEST.in

@@ -0,0 +1,6 @@
+include requirements.txt
+include README.rst
+include LICENSE
+include HISTORY.rst
+include AUTHORS
+include test_requests_gssapi.py

+ 381 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/PKG-INFO

@@ -0,0 +1,381 @@
+Metadata-Version: 1.1
+Name: requests-gssapi
+Version: 1.2.2
+Summary: A GSSAPI authentication handler for python-requests
+Home-page: https://github.com/pythongssapi/requests-gssapi
+Author: Ian Cordasco, Cory Benfield, Michael Komitee, Robbie Harwood
+Author-email: rharwood@redhat.com
+License: UNKNOWN
+Description: 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.
+        
+        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.
+        
+        
+        History
+        =======
+        
+        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
+        
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: ISC License (ISCL)

+ 227 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/README.rst

@@ -0,0 +1,227 @@
+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.
+
+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.

+ 25 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/requests_gssapi/__init__.py

@@ -0,0 +1,25 @@
+"""
+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.2'

+ 73 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/requests_gssapi/compat.py

@@ -0,0 +1,73 @@
+"""
+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))

+ 20 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/requests_gssapi/exceptions.py

@@ -0,0 +1,20 @@
+"""
+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

+ 327 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/requests_gssapi/gssapi_.py

@@ -0,0 +1,327 @@
+import 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 <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

+ 2 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/requirements.txt

@@ -0,0 +1,2 @@
+requests>=1.1.0
+gssapi

+ 7 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/setup.cfg

@@ -0,0 +1,7 @@
+[wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+

+ 59 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/setup.py

@@ -0,0 +1,59 @@
+#!/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',
+    tests_require=['mock'],
+    classifiers=[
+        "License :: OSI Approved :: ISC License (ISCL)"
+    ],
+)

+ 650 - 0
desktop/core/ext-py/requests-gssapi-1.2.2/test_requests_gssapi.py

@@ -0,0 +1,650 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""Tests for requests_gssapi."""
+
+from base64 import b64encode
+from 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()