diff options
author | Carlos Aguado <caguadosa@gmail.com> | 2014-02-03 13:48:47 +0100 |
---|---|---|
committer | Carlos Aguado <caguadosa@gmail.com> | 2014-02-04 09:22:42 +0100 |
commit | 1242e60bdd250dd31f49a181dd326af96427f300 (patch) | |
tree | 2763d80fb63960c934965606ea2e436e6cfd8777 | |
parent | 5db69f3f6616ea199a7840f0602b988f8d5504b9 (diff) | |
download | git-repo-1242e60bdd250dd31f49a181dd326af96427f300.tar.gz |
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
-rwxr-xr-x | main.py | 87 |
1 files changed, 87 insertions, 0 deletions
@@ -31,6 +31,11 @@ else: | |||
31 | urllib = imp.new_module('urllib') | 31 | urllib = imp.new_module('urllib') |
32 | urllib.request = urllib2 | 32 | urllib.request = urllib2 |
33 | 33 | ||
34 | try: | ||
35 | import kerberos | ||
36 | except ImportError: | ||
37 | kerberos = None | ||
38 | |||
34 | from trace import SetTrace | 39 | from trace import SetTrace |
35 | from git_command import git, GitCommand | 40 | from git_command import git, GitCommand |
36 | from git_config import init_ssh, close_ssh | 41 | from git_config import init_ssh, close_ssh |
@@ -332,6 +337,86 @@ class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler): | |||
332 | self.retried = 0 | 337 | self.retried = 0 |
333 | raise | 338 | raise |
334 | 339 | ||
340 | class _KerberosAuthHandler(urllib.request.BaseHandler): | ||
341 | def __init__(self): | ||
342 | self.retried = 0 | ||
343 | self.context = None | ||
344 | self.handler_order = urllib.request.BaseHandler.handler_order - 50 | ||
345 | |||
346 | def http_error_401(self, req, fp, code, msg, headers): | ||
347 | host = req.get_host() | ||
348 | retry = self.http_error_auth_reqed('www-authenticate', host, req, headers) | ||
349 | return retry | ||
350 | |||
351 | def http_error_auth_reqed(self, auth_header, host, req, headers): | ||
352 | try: | ||
353 | spn = "HTTP@%s" % host | ||
354 | authdata = self._negotiate_get_authdata(auth_header, headers) | ||
355 | |||
356 | if self.retried > 3: | ||
357 | raise urllib.request.HTTPError(req.get_full_url(), 401, | ||
358 | "Negotiate auth failed", headers, None) | ||
359 | else: | ||
360 | self.retried += 1 | ||
361 | |||
362 | neghdr = self._negotiate_get_svctk(spn, authdata) | ||
363 | if neghdr is None: | ||
364 | return None | ||
365 | |||
366 | req.add_unredirected_header('Authorization', neghdr) | ||
367 | response = self.parent.open(req) | ||
368 | |||
369 | srvauth = self._negotiate_get_authdata(auth_header, response.info()) | ||
370 | if self._validate_response(srvauth): | ||
371 | return response | ||
372 | except kerberos.GSSError: | ||
373 | return None | ||
374 | except: | ||
375 | self.reset_retry_count() | ||
376 | raise | ||
377 | finally: | ||
378 | self._clean_context() | ||
379 | |||
380 | def reset_retry_count(self): | ||
381 | self.retried = 0 | ||
382 | |||
383 | def _negotiate_get_authdata(self, auth_header, headers): | ||
384 | authhdr = headers.get(auth_header, None) | ||
385 | if authhdr is not None: | ||
386 | for mech_tuple in authhdr.split(","): | ||
387 | mech, __, authdata = mech_tuple.strip().partition(" ") | ||
388 | if mech.lower() == "negotiate": | ||
389 | return authdata.strip() | ||
390 | return None | ||
391 | |||
392 | def _negotiate_get_svctk(self, spn, authdata): | ||
393 | if authdata is None: | ||
394 | return None | ||
395 | |||
396 | result, self.context = kerberos.authGSSClientInit(spn) | ||
397 | if result < kerberos.AUTH_GSS_COMPLETE: | ||
398 | return None | ||
399 | |||
400 | result = kerberos.authGSSClientStep(self.context, authdata) | ||
401 | if result < kerberos.AUTH_GSS_CONTINUE: | ||
402 | return None | ||
403 | |||
404 | response = kerberos.authGSSClientResponse(self.context) | ||
405 | return "Negotiate %s" % response | ||
406 | |||
407 | def _validate_response(self, authdata): | ||
408 | if authdata is None: | ||
409 | return None | ||
410 | result = kerberos.authGSSClientStep(self.context, authdata) | ||
411 | if result == kerberos.AUTH_GSS_COMPLETE: | ||
412 | return True | ||
413 | return None | ||
414 | |||
415 | def _clean_context(self): | ||
416 | if self.context is not None: | ||
417 | kerberos.authGSSClientClean(self.context) | ||
418 | self.context = None | ||
419 | |||
335 | def init_http(): | 420 | def init_http(): |
336 | handlers = [_UserAgentHandler()] | 421 | handlers = [_UserAgentHandler()] |
337 | 422 | ||
@@ -348,6 +433,8 @@ def init_http(): | |||
348 | pass | 433 | pass |
349 | handlers.append(_BasicAuthHandler(mgr)) | 434 | handlers.append(_BasicAuthHandler(mgr)) |
350 | handlers.append(_DigestAuthHandler(mgr)) | 435 | handlers.append(_DigestAuthHandler(mgr)) |
436 | if kerberos: | ||
437 | handlers.append(_KerberosAuthHandler()) | ||
351 | 438 | ||
352 | if 'http_proxy' in os.environ: | 439 | if 'http_proxy' in os.environ: |
353 | url = os.environ['http_proxy'] | 440 | url = os.environ['http_proxy'] |