Revision: 1542 http://trac.macosforge.org/projects/calendarserver/changeset/1542 Author: cdaboo@apple.com Date: 2007-05-21 11:23:56 -0700 (Mon, 21 May 2007) Log Message: ----------- Have the server auto-detect its own Kerberos principal given the hostname. Modified Paths: -------------- CalendarServer/trunk/run CalendarServer/trunk/twistedcaldav/authkerb.py CalendarServer/trunk/twistedcaldav/tap.py CalendarServer/trunk/twistedcaldav/test/test_kerberos.py CalendarServer/trunk/twistedcaldav/test/test_tap.py Modified: CalendarServer/trunk/run =================================================================== --- CalendarServer/trunk/run 2007-05-21 18:01:59 UTC (rev 1541) +++ CalendarServer/trunk/run 2007-05-21 18:23:56 UTC (rev 1542) @@ -464,7 +464,7 @@ if ! py_have_module kerberos; then kerberos="${top}/PyKerberos"; - svn_get "PyKerberos" "${kerberos}" "${svn_uri_base}/PyKerberos/trunk" 1213; + svn_get "PyKerberos" "${kerberos}" "${svn_uri_base}/PyKerberos/trunk" 1541; py_build "PyKerberos" "${kerberos}" false; # FIXME: make optional py_install "PyKerberos" "${kerberos}"; Modified: CalendarServer/trunk/twistedcaldav/authkerb.py =================================================================== --- CalendarServer/trunk/twistedcaldav/authkerb.py 2007-05-21 18:01:59 UTC (rev 1541) +++ CalendarServer/trunk/twistedcaldav/authkerb.py 2007-05-21 18:23:56 UTC (rev 1542) @@ -55,6 +55,17 @@ """ def __init__(self, username, password, service, realm): + """ + + @param username: user name of user to authenticate + @type username: str + @param password: password for user being authenticated + @type password: str + @param service: service principal + @type service: str + @param hostname: realm + @type hostname: str + """ credentials.UsernamePassword.__init__(self, username, password) # Convert Kerberos principal spec into service and realm @@ -73,15 +84,43 @@ scheme = 'basic' - def __init__(self, service, realm): + def __init__(self, principal=None, type=None, hostname=None): """ - The realm string can be of the form service/realm@domain. We split that - into service@domain, and realm. + + @param principal: full Kerberos principal (e.g., 'http/server.example.com@EXAMPLE.COM'). If C{None} + then the type and hostname arguments are used instead. + @type service: str + @param type: service type for Kerberos (e.g., 'http'). Must be C{None} if principal used. + @type type: str + @param hostname: hostname for this server. Must be C{None} if principal used. + @type hostname: str """ - self.service = service + + # Only certain combinations of arguments allowed + assert (principal and not type and not hostname) or (not principal and type and hostname) + + if not principal: + # Look up the Kerberos principal given the service type and hostname, and extract + # the realm and a service principal value for later use. + try: + principal = kerberos.getServerPrincipalDetails(type, hostname) + except kerberos.KrbError, ex: + logging.err("getServerPrincipalDetails: %s" % (ex[0],), system="BasicKerberosCredentialFactory") + raise ValueError('Authentication System Failure: %s' % (ex[0],)) + + try: + splits = principal.split("/") + servicetype = splits[0] + splits = splits[1].split("@") + realm = splits[1] + except IndexError: + logging.err("Invalid Kerberos principal: %s" % (principal,), system="BasicKerberosCredentialFactory") + raise ValueError('Authentication System Failure: Invalid Kerberos principal: %s' % (principal,)) + + self.service = "%s@%s" % (servicetype, realm,) self.realm = realm - def getChallenge(self, peer): + def getChallenge(self, _ignore_peer): return {'realm': self.realm} def decode(self, response, request): #@UnusedVariable @@ -142,19 +181,50 @@ scheme = 'negotiate' - def __init__(self, service, realm): + def __init__(self, principal=None, type=None, hostname=None): + """ + + @param principal: full Kerberos principal (e.g., 'http/server.example.com@EXAMPLE.COM'). If C{None} + then the type and hostname arguments are used instead. + @type service: str + @param type: service type for Kerberos (e.g., 'http'). Must be C{None} if principal used. + @type type: str + @param hostname: hostname for this server. Must be C{None} if principal used. + @type hostname: str + """ - self.service = service + # Only certain combinations of arguments allowed + assert (principal and not type and not hostname) or (not principal and type and hostname) + + if not principal: + # Look up the Kerberos principal given the service type and hostname, and extract + # the realm and a service principal value for later use. + try: + principal = kerberos.getServerPrincipalDetails(type, hostname) + except kerberos.KrbError, ex: + logging.err("getServerPrincipalDetails: %s" % (ex[0],), system="NegotiateCredentialFactory") + raise ValueError('Authentication System Failure: %s' % (ex[0],)) + + try: + splits = principal.split("/") + servicetype = splits[0] + splits = splits[1].split("@") + realm = splits[1] + except IndexError: + logging.err("Invalid Kerberos principal: %s" % (principal,), system="NegotiateCredentialFactory") + raise ValueError('Authentication System Failure: Invalid Kerberos principal: %s' % (principal,)) + + self.service = "%s@%s" % (servicetype, realm,) self.realm = realm - def getChallenge(self, peer): + def getChallenge(self, _ignore_peer): return {} def decode(self, base64data, request): # Init GSSAPI first try: - result, context = kerberos.authGSSServerInit(self.service); + _ignore_result, context = kerberos.authGSSServerInit(self.service); except kerberos.GSSError, ex: logging.err("authGSSServerInit: %s(%s)" % (ex[0][0], ex[1][0],), system="NegotiateCredentialFactory") raise error.LoginFailed('Authentication System Failure: %s(%s)' % (ex[0][0], ex[1][0],)) @@ -191,7 +261,7 @@ # Close the context try: - result = kerberos.authGSSServerClean(context); + kerberos.authGSSServerClean(context); except kerberos.GSSError, ex: logging.err("authGSSServerClean: %s" % (ex[0][0], ex[1][0],), system="NegotiateCredentialFactory") raise error.LoginFailed('Authentication System Failure %s(%s)' % (ex[0][0], ex[1][0],)) Modified: CalendarServer/trunk/twistedcaldav/tap.py =================================================================== --- CalendarServer/trunk/twistedcaldav/tap.py 2007-05-21 18:01:59 UTC (rev 1541) +++ CalendarServer/trunk/twistedcaldav/tap.py 2007-05-21 18:23:56 UTC (rev 1542) @@ -415,18 +415,12 @@ log.msg("Kerberos support not available") continue - service = schemeConfig['ServicePrincipal'] - - if '@' in service: - rest, kerbRealm = service.split('@', 1) + principal = schemeConfig['ServicePrincipal'] + if not principal: + credFactory = NegotiateCredentialFactory(type="http", hostname=config.ServerHostName) else: - kerbRealm = config.ServerHostName + credFactory = NegotiateCredentialFactory(principal=principal) - credFactory = NegotiateCredentialFactory( - service, - kerbRealm - ) - elif scheme == 'digest': credFactory = QopDigestCredentialFactory( schemeConfig['Algorithm'], Modified: CalendarServer/trunk/twistedcaldav/test/test_kerberos.py =================================================================== --- CalendarServer/trunk/twistedcaldav/test/test_kerberos.py 2007-05-21 18:01:59 UTC (rev 1541) +++ CalendarServer/trunk/twistedcaldav/test/test_kerberos.py 2007-05-21 18:23:56 UTC (rev 1542) @@ -34,18 +34,25 @@ authkerb.BasicKerberosCredentials("test", "test", "http/example.com@EXAMPLE.COM", "EXAMPLE.COM") def test_BasicKerberosCredentialFactory(self): - factory = authkerb.BasicKerberosCredentialFactory("http/example.com@EXAMPLE.COM", "EXAMPLE.COM") + factory = authkerb.BasicKerberosCredentialFactory(principal="http/server.example.com@EXAMPLE.COM") challenge = factory.getChallenge("peer") expected_challenge = {'realm': "EXAMPLE.COM"} self.assertTrue(challenge == expected_challenge, msg="BasicKerberosCredentialFactory challenge %s != %s" % (challenge, expected_challenge)) + def test_BasicKerberosCredentialFactoryInvalidPrincipal(self): + self.assertRaises( + ValueError, + authkerb.BasicKerberosCredentialFactory, + principal="http/server.example.com/EXAMPLE.COM" + ) + def test_NegotiateCredentials(self): authkerb.NegotiateCredentials("test") def test_NegotiateCredentialFactory(self): - factory = authkerb.NegotiateCredentialFactory("http/example.com@EXAMPLE.COM", "EXAMPLE.COM") + factory = authkerb.NegotiateCredentialFactory(principal="http/server.example.com@EXAMPLE.COM") challenge = factory.getChallenge("peer") expected_challenge = {} @@ -61,3 +68,10 @@ self.fail(msg="NegotiateCredentialFactory decode failed with exception: %s" % (ex,)) else: self.fail(msg="NegotiateCredentialFactory decode did not fail") + + def test_NegotiateCredentialFactoryInvalidPrincipal(self): + self.assertRaises( + ValueError, + authkerb.NegotiateCredentialFactory, + principal="http/server.example.com/EXAMPLE.COM" + ) Modified: CalendarServer/trunk/twistedcaldav/test/test_tap.py =================================================================== --- CalendarServer/trunk/twistedcaldav/test/test_tap.py 2007-05-21 18:01:59 UTC (rev 1541) +++ CalendarServer/trunk/twistedcaldav/test/test_tap.py 2007-05-21 18:23:56 UTC (rev 1542) @@ -413,6 +413,7 @@ """ self.config['Authentication']['Digest']['Enabled'] = True self.config['Authentication']['Kerberos']['Enabled'] = True + self.config['Authentication']['Kerberos']['ServicePrincipal'] = 'http/hello@bob' self.config['Authentication']['Basic']['Enabled'] = True self.writeConfig() @@ -432,23 +433,18 @@ self.assertEquals(len(expectedSchemes), len(authWrapper.credentialFactories)) - def test_servicePrincipalNoRealm(self): + def test_servicePrincipalNone(self): """ - Test that the Kerberos Realm defaults to the ServerHostName when - the principal is not in the form of proto/host@realm + Test that the Kerberos principal look is attempted if the principal is empty. """ - self.config['Authentication']['Kerberos']['ServicePrincipal'] = 'http/hello' + self.config['Authentication']['Kerberos']['ServicePrincipal'] = '' self.config['Authentication']['Kerberos']['Enabled'] = True self.writeConfig() - site = self.getSite() + self.assertRaises( + ValueError, + self.getSite) - authWrapper = site.resource.resource - - ncf = authWrapper.credentialFactories['negotiate'] - self.assertEquals(ncf.service, 'http/hello') - self.assertEquals(ncf.realm, 'localhost') - - def test_servicePrincipalWithRealm(self): + def test_servicePrincipal(self): """ Test that the kerberos realm is the realm portion of a principal in the form proto/host@realm @@ -461,7 +457,7 @@ authWrapper = site.resource.resource ncf = authWrapper.credentialFactories['negotiate'] - self.assertEquals(ncf.service, 'http/hello@bob') + self.assertEquals(ncf.service, 'http@bob') self.assertEquals(ncf.realm, 'bob') def test_AuthWrapperPartialEnabled(self):