Commits

Dominik Ruf committed 283734a

init

Comments (0)

Files changed (3)

+syntax: glob
+.project
+.pydevproject
+*.pyc
+*.pyo
+Mercurial SSO Authentication
+============================
+
+This mecurial extension allows you to use single sign-on authenticate with
+web servers that use NTLM or kerberos authentication.
+
+Either the kerberos or sspi (from pywin32) python packages have to be
+available in your mecurial installation. 
+
+I tested it with
+::
+    Windows client -> Windows Server with mod_auth_sspi    (works)
+    Ubuntu client  -> Windows Server with mod_auth_sspi    (doesn't work)
+    Windows client -> Ubuntu Server with mod_auth_kerb     (works)
+    Ubuntu client  -> Ubuntu Server with mod_auth_kerb     (works)
+
+Installation
+------------
+
+To use this extension simple add it to your mercurial.ini like this:
+::
+    [extensions]
+    hgssoauthentication=c:\path\to\hgssoauthentication.py

hgssoauthentication.py

+# Copyright 2012 Dominik Ruf <dominikruf@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+#
+# This mecurial extension provides single sign-on authenticate for NTLM or
+# kerberos web servers. Either the kerberos or sspi (from pywin32) modules
+# have to be available in your mecurial installation. 
+# 
+# I tested it with
+# Windows client -> Windows Server with mod_auth_sspi    (works)
+# Ubuntu client  -> Windows Server with mod_auth_sspi    (doesn't work)
+# Windows client -> Ubuntu Server with mod_auth_kerb     (works)
+# Ubuntu client  -> Ubuntu Server with mod_auth_kerb     (works)
+# 
+
+
+import mercurial.url
+
+import urllib2
+import httplib
+import os
+from base64 import encodestring, decodestring
+
+
+try:
+    from sspi import ClientAuth
+except ImportError, e:
+    try:
+        import kerberos
+    except ImportError, e:
+        raise ImportError('Neigther sspi nor kerberos module is available')
+
+class SSPIAuthHandler(urllib2.BaseHandler):
+    """auth handler for urllib2 that does Kerberos/NTLM/SSPI HTTP Negotiate Authentication
+    """
+    
+    handler_order = 480 # TODO: test this by enabling basic auth 
+    
+    def __init__(self, ui, passmgr):
+        pass
+    
+    def http_error_401(self, req, fp, code, msg, headers):
+        supported_schemes = [s.strip() for s in headers.get("WWW-Authenticate", "").split(",")]
+        
+        if('NTLM' in supported_schemes):
+            # 1. request
+            ca = ClientAuth("NTLM", auth_info=None)
+            auth_scheme = ca.pkg_info['Name']
+            out_buf = ca.authorize(None)[1]
+            data = out_buf[0].Buffer
+            auth = encodestring(data).replace("\012", "")
+            # since urllib2 doesn't support keepalive we create our own new connection
+            h = httplib.HTTPConnection(req.host)
+            h.putrequest('GET', req._Request__r_host)
+            h.putheader('Authorization', auth_scheme + ' ' + auth)
+            h.putheader('Content-Length', '0')
+            h.endheaders()
+            resp = h.getresponse()
+            
+            # 2.request
+            schemes = [s.strip() for s in resp.msg.get("WWW-Authenticate", "").split(",")]
+            for scheme in schemes:
+                if scheme.startswith(auth_scheme):
+                    data = decodestring(scheme[len(auth_scheme)+1:])
+                    break
+            resp.read()
+            
+            out_buf = ca.authorize(data)[1]
+            data = out_buf[0].Buffer
+            
+            auth = encodestring(data).replace("\012", "")
+            h.putrequest('GET', req._Request__r_host)
+            h.putheader('Authorization', auth_scheme + ' ' + auth)
+            h.putheader('Content-Length', '0')
+            for e in req.unredirected_hdrs.items() + req.headers.items():
+                if(e[0] != 'Host'):
+                    h.putheader(e[0], e[1])
+            h.endheaders()
+            resp = h.getresponse()
+            
+            fp.headers = resp.msg
+            fp.read = resp.read
+            return fp
+        
+        elif('Negotiate' in supported_schemes):
+            ca = ClientAuth("Kerberos", targetspn='HTTP/%s@%s' % (req.host, os.environ['USERDNSDOMAIN']), auth_info=None)
+            out_buf = ca.authorize(None)[1]
+            data = out_buf[0].Buffer
+            auth = encodestring(data).replace("\012", "")
+            req.add_header('Authorization', 'Negotiate' + ' ' + auth)
+            return self.parent.open(req)
+
+class KerberosAuthHandler(urllib2.BaseHandler):
+    """auth handler for urllib2 that does Kerberos HTTP Negotiate Authentication
+    """
+
+    handler_order = 480
+
+    def __init__(self, ui, passmgr):
+        pass
+
+    def http_error_401(self, req, fp, code, msg, headers):
+        supported_schemes = [s.strip() for s in headers.get("WWW-Authenticate", "").split(",")]
+
+        context = kerberos.authGSSClientInit("HTTP@%s" % req.get_host())[1]
+        kerberos.authGSSClientStep(context, supported_schemes[0])
+        response = kerberos.authGSSClientResponse(context)
+        req.add_unredirected_header('Authorization', "Negotiate %s" % response)
+        resp = self.parent.open(req)
+        # make sure the response came from the correct server
+        server_token = resp.info().get("WWW-Authenticate", "").split(",")[0].split()[1]
+        server_auth_result = kerberos.authGSSClientStep(context, server_token)
+        if(server_auth_result < 1):
+            raise Exception("Server authentication error: %s" % str(server_auth_result))
+        return resp
+            
+def uisetup(ui):
+    if('ClientAuth' in globals()):
+        mercurial.url.handlerfuncs.append(SSPIAuthHandler)
+    elif('kerberos' in globals()):
+        mercurial.url.handlerfuncs.append(KerberosAuthHandler)
+