Implement Kerberos HTTP authentication handler

This commit implements a Kerberos HTTP authentication handler. It
uses credentials from a local cache to perform an HTTP authentication
negotiation using the GSSAPI.

The purpose of this handler is to allow the use Kerberos authentication
to access review endpoints without the need to transmit the user
password.

Change-Id: Id2c3fc91a58b15a3e83e4bd9ca87203fa3d647c8
This commit is contained in:
Carlos Aguado 2014-02-03 13:48:47 +01:00
parent 5db69f3f66
commit 1242e60bdd

87
main.py
View File

@ -31,6 +31,11 @@ else:
urllib = imp.new_module('urllib') urllib = imp.new_module('urllib')
urllib.request = urllib2 urllib.request = urllib2
try:
import kerberos
except ImportError:
kerberos = None
from trace import SetTrace from trace import SetTrace
from git_command import git, GitCommand from git_command import git, GitCommand
from git_config import init_ssh, close_ssh from git_config import init_ssh, close_ssh
@ -332,6 +337,86 @@ class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
self.retried = 0 self.retried = 0
raise raise
class _KerberosAuthHandler(urllib.request.BaseHandler):
def __init__(self):
self.retried = 0
self.context = None
self.handler_order = urllib.request.BaseHandler.handler_order - 50
def http_error_401(self, req, fp, code, msg, headers):
host = req.get_host()
retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
return retry
def http_error_auth_reqed(self, auth_header, host, req, headers):
try:
spn = "HTTP@%s" % host
authdata = self._negotiate_get_authdata(auth_header, headers)
if self.retried > 3:
raise urllib.request.HTTPError(req.get_full_url(), 401,
"Negotiate auth failed", headers, None)
else:
self.retried += 1
neghdr = self._negotiate_get_svctk(spn, authdata)
if neghdr is None:
return None
req.add_unredirected_header('Authorization', neghdr)
response = self.parent.open(req)
srvauth = self._negotiate_get_authdata(auth_header, response.info())
if self._validate_response(srvauth):
return response
except kerberos.GSSError:
return None
except:
self.reset_retry_count()
raise
finally:
self._clean_context()
def reset_retry_count(self):
self.retried = 0
def _negotiate_get_authdata(self, auth_header, headers):
authhdr = headers.get(auth_header, None)
if authhdr is not None:
for mech_tuple in authhdr.split(","):
mech, __, authdata = mech_tuple.strip().partition(" ")
if mech.lower() == "negotiate":
return authdata.strip()
return None
def _negotiate_get_svctk(self, spn, authdata):
if authdata is None:
return None
result, self.context = kerberos.authGSSClientInit(spn)
if result < kerberos.AUTH_GSS_COMPLETE:
return None
result = kerberos.authGSSClientStep(self.context, authdata)
if result < kerberos.AUTH_GSS_CONTINUE:
return None
response = kerberos.authGSSClientResponse(self.context)
return "Negotiate %s" % response
def _validate_response(self, authdata):
if authdata is None:
return None
result = kerberos.authGSSClientStep(self.context, authdata)
if result == kerberos.AUTH_GSS_COMPLETE:
return True
return None
def _clean_context(self):
if self.context is not None:
kerberos.authGSSClientClean(self.context)
self.context = None
def init_http(): def init_http():
handlers = [_UserAgentHandler()] handlers = [_UserAgentHandler()]
@ -348,6 +433,8 @@ def init_http():
pass pass
handlers.append(_BasicAuthHandler(mgr)) handlers.append(_BasicAuthHandler(mgr))
handlers.append(_DigestAuthHandler(mgr)) handlers.append(_DigestAuthHandler(mgr))
if kerberos:
handlers.append(_KerberosAuthHandler())
if 'http_proxy' in os.environ: if 'http_proxy' in os.environ:
url = os.environ['http_proxy'] url = os.environ['http_proxy']