[CalendarServer-changes] [3470] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Dec 5 19:56:15 PST 2008


Revision: 3470
          http://trac.macosforge.org/projects/calendarserver/changeset/3470
Author:   cdaboo at apple.com
Date:     2008-12-05 19:56:15 -0800 (Fri, 05 Dec 2008)
Log Message:
-----------
Merged digest creds cache branch to trunk.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/trunk/twistedcaldav/authkerb.py
    CalendarServer/trunk/twistedcaldav/directory/digest.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py
    CalendarServer/trunk/twistedcaldav/directory/wiki.py
    CalendarServer/trunk/twistedcaldav/root.py
    CalendarServer/trunk/twistedcaldav/test/test_kerberos.py
    CalendarServer/trunk/twistedcaldav/test/test_root.py

Added Paths:
-----------
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -557,7 +557,6 @@
                         schemeConfig["Algorithm"],
                         schemeConfig["Qop"],
                         realm,
-                        config.DataRoot,
                     )
 
                 elif scheme == "basic":

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch (from rev 3469, CalendarServer/branches/users/cdaboo/digestdb-fix-3445/lib-patches/Twisted/twisted.web2.auth.basic.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.basic.patch	2008-12-06 03:56:15 UTC (rev 3470)
@@ -0,0 +1,29 @@
+Index: twisted/web2/auth/basic.py
+===================================================================
+--- twisted/web2/auth/basic.py	(revision 19773)
++++ twisted/web2/auth/basic.py	(working copy)
+@@ -1,6 +1,7 @@
+ # -*- test-case-name: twisted.web2.test.test_httpauth -*-
+ 
+ from twisted.cred import credentials, error
++from twisted.internet.defer import succeed
+ from twisted.web2.auth.interfaces import ICredentialFactory
+ 
+ from zope.interface import implements
+@@ -18,7 +19,7 @@
+         self.realm = realm
+ 
+     def getChallenge(self, peer):
+-        return {'realm': self.realm}
++        return succeed({'realm': self.realm})
+ 
+     def decode(self, response, request):
+         try:
+@@ -28,6 +29,6 @@
+ 
+         creds = creds.split(':', 1)
+         if len(creds) == 2:
+-            return credentials.UsernamePassword(*creds)
++            return succeed(credentials.UsernamePassword(*creds))
+         else:
+             raise error.LoginFailed('Invalid credentials')

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.digest.patch	2008-12-06 03:56:15 UTC (rev 3470)
@@ -2,7 +2,11 @@
 ===================================================================
 --- twisted/web2/auth/digest.py	(revision 19773)
 +++ twisted/web2/auth/digest.py	(working copy)
-@@ -11,16 +11,24 @@
+@@ -8,19 +8,28 @@
+ import time
+ 
+ from twisted.cred import credentials, error
++from twisted.internet.defer import succeed
  from zope.interface import implements, Interface
  
  from twisted.web2.auth.interfaces import ICredentialFactory
@@ -31,7 +35,7 @@
  }
  
  # DigestCalcHA1
-@@ -228,7 +236,7 @@
+@@ -228,7 +237,7 @@
          # Now, what we do is encode the nonce, client ip and a timestamp
          # in the opaque value with a suitable digest
          key = "%s,%s,%s" % (nonce, clientip, str(int(self._getTime())))
@@ -40,7 +44,7 @@
          ekey = key.encode('base64')
          return "%s-%s" % (digest, ekey.strip('\n'))
  
-@@ -274,7 +282,7 @@
+@@ -274,7 +283,7 @@
                  'Invalid response, incompatible opaque/nonce too old')
  
          # Verify the digest
@@ -49,7 +53,25 @@
          if digest != opaqueParts[0]:
              raise error.LoginFailed('Invalid response, invalid opaque value')
  
-@@ -315,18 +323,18 @@
+@@ -293,11 +302,12 @@
+         c = self.generateNonce()
+         o = self.generateOpaque(c, peer.host)
+ 
+-        return {'nonce': c,
+-                'opaque': o,
+-                'qop': 'auth',
+-                'algorithm': self.algorithm,
+-                'realm': self.realm}
++        return succeed({'nonce': c,
++            'opaque': o,
++            'qop': 'auth',
++            'algorithm': self.algorithm,
++            'realm': self.realm,
++        })
+ 
+     def decode(self, response, request):
+         """
+@@ -315,18 +325,18 @@
          @raise: L{error.LoginFailed} if the response does not contain a
              username, a nonce, an opaque, or if the opaque is invalid.
          """
@@ -79,3 +101,13 @@
          username = auth.get('username')
          if not username:
              raise error.LoginFailed('Invalid response, no username given.')
+@@ -342,7 +352,7 @@
+                              auth.get('nonce'),
+                              request.remoteAddr.host):
+ 
+-            return DigestedCredentials(username,
++            return succeed(DigestedCredentials(username,
+                                        request.method,
+                                        self.realm,
+-                                       auth)
++                                       auth))

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch (from rev 3469, CalendarServer/branches/users/cdaboo/digestdb-fix-3445/lib-patches/Twisted/twisted.web2.auth.interfaces.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.interfaces.patch	2008-12-06 03:56:15 UTC (rev 3470)
@@ -0,0 +1,22 @@
+Index: twisted/web2/auth/interfaces.py
+===================================================================
+--- twisted/web2/auth/interfaces.py	(revision 19773)
++++ twisted/web2/auth/interfaces.py	(working copy)
+@@ -18,7 +18,7 @@
+         @param peer: The client's address
+ 
+         @rtype: C{dict}
+-        @return: dictionary of challenge arguments
++        @return: deferred returning dictionary of challenge arguments
+         """
+ 
+     def decode(response, request):
+@@ -32,7 +32,7 @@
+         @type request: L{twisted.web2.server.Request}
+         @param request: the request being processed
+ 
+-        @return: ICredentials
++        @return: deferred returning ICredentials
+         """
+ 
+ 

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch (from rev 3469, CalendarServer/branches/users/cdaboo/digestdb-fix-3445/lib-patches/Twisted/twisted.web2.auth.wrapper.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.auth.wrapper.patch	2008-12-06 03:56:15 UTC (rev 3470)
@@ -0,0 +1,205 @@
+Index: twisted/web2/auth/wrapper.py
+===================================================================
+--- twisted/web2/auth/wrapper.py	(revision 19773)
++++ twisted/web2/auth/wrapper.py	(working copy)
+@@ -5,32 +5,45 @@
+ """
+ from zope.interface import implements, directlyProvides
+ from twisted.cred import error, credentials
+-from twisted.python import failure
+ from twisted.web2 import responsecode
+ from twisted.web2 import http
+ from twisted.web2 import iweb
+ from twisted.web2.auth.interfaces import IAuthenticatedRequest
++from twisted.internet.defer import inlineCallbacks, returnValue
+ 
+ class UnauthorizedResponse(http.StatusResponse):
+     """A specialized response class for generating www-authenticate headers
+     from the given L{CredentialFactory} instances
+     """
+ 
+-    def __init__(self, factories, remoteAddr=None):
++    @staticmethod
++    def makeResponse(factories, remoteAddr=None):
++        
++        response = UnauthorizedResponse()
++        d = response.generateHeaders(factories, remoteAddr)
++        d.addCallback(lambda _:response)
++        return d
++
++    def __init__(self):
++
++        super(UnauthorizedResponse, self).__init__(
++            responsecode.UNAUTHORIZED,
++            "You are not authorized to access this resource."
++    )
++
++    @inlineCallbacks
++    def generateHeaders(self, factories, remoteAddr=None):
+         """
+         @param factories: A L{dict} of {'scheme': ICredentialFactory}
+ 
+         @param remoteAddr: An L{IAddress} for the connecting client.
+         """
+ 
+-        super(UnauthorizedResponse, self).__init__(
+-            responsecode.UNAUTHORIZED,
+-            "You are not authorized to access this resource.")
+-
+         authHeaders = []
+         for factory in factories.itervalues():
+-            authHeaders.append((factory.scheme,
+-                                factory.getChallenge(remoteAddr)))
++            scheme = factory.scheme
++            challenge = (yield factory.getChallenge(remoteAddr))
++            authHeaders.append((scheme, challenge,))
+ 
+         self.headers.setHeader('www-authenticate', authHeaders)
+ 
+@@ -71,8 +84,6 @@
+ 
+     def _loginSucceeded(self, avatar, request):
+         """
+-        Callback for successful login.
+-
+         @param avatar: A tuple of the form (interface, avatar) as
+             returned by your realm.
+ 
+@@ -85,6 +96,7 @@
+ 
+         directlyProvides(request, IAuthenticatedRequest)
+ 
++        @inlineCallbacks
+         def _addAuthenticateHeaders(request, response):
+             """
+             A response filter that adds www-authenticate headers
+@@ -93,14 +105,16 @@
+             """
+             if response.code == responsecode.UNAUTHORIZED:
+                 if not response.headers.hasHeader('www-authenticate'):
+-                    newResp = UnauthorizedResponse(self.credentialFactories,
+-                                                   request.remoteAddr)
++                    newResp = (yield UnauthorizedResponse.makeResponse(
++                        self.credentialFactories,
++                        request.remoteAddr
++                    ))
+ 
+                     response.headers.setHeader(
+                         'www-authenticate',
+                         newResp.headers.getHeader('www-authenticate'))
+ 
+-            return response
++            returnValue(response)
+ 
+         _addAuthenticateHeaders.handleErrors = True
+ 
+@@ -108,27 +122,22 @@
+ 
+         return self.wrappedResource
+ 
+-    def _loginFailed(self, result, request):
++    @inlineCallbacks
++    def _loginFailed(self, request):
+         """
+-        Errback for failed login.
+-
+-        @param result: L{Failure} returned by portal.login
+-
+         @param request: L{IRequest} that encapsulates this auth
+             attempt.
+ 
+-        @return: A L{Failure} containing an L{HTTPError} containing the
+-            L{UnauthorizedResponse} if C{result} is an L{UnauthorizedLogin}
+-            or L{UnhandledCredentials} error
++        @raise: always rais HTTPError
+         """
+-        result.trap(error.UnauthorizedLogin, error.UnhandledCredentials)
+ 
+-        return failure.Failure(
+-            http.HTTPError(
+-                UnauthorizedResponse(
+-                self.credentialFactories,
+-                request.remoteAddr)))
++        response = (yield UnauthorizedResponse.makeResponse(
++            self.credentialFactories,
++            request.remoteAddr
++        ))
++        raise http.HTTPError(response)
+ 
++    @inlineCallbacks
+     def login(self, factory, response, request):
+         """
+         @param factory: An L{ICredentialFactory} that understands the given
+@@ -142,50 +151,48 @@
+             or a failure containing an L{UnauthorizedResponse}
+         """
+         try:
+-            creds = factory.decode(response, request)
++            creds = (yield factory.decode(response, request))
+         except error.LoginFailed:
+-            raise http.HTTPError(UnauthorizedResponse(
+-                                    self.credentialFactories,
+-                                    request.remoteAddr))
++            yield self._loginFailed(request)
+ 
++        try:
++            avatar = (yield self.portal.login(creds, None, *self.interfaces))
++        except (error.UnauthorizedLogin, error.UnhandledCredentials):
++            yield self._loginFailed(request)
++        resource = self._loginSucceeded(avatar, request)
++        returnValue(resource)
+ 
+-        return self.portal.login(creds, None, *self.interfaces
+-                                ).addCallbacks(self._loginSucceeded,
+-                                               self._loginFailed,
+-                                               (request,), None,
+-                                               (request,), None)
+-
++    @inlineCallbacks
+     def authenticate(self, request):
+         """
+-        Attempt to authenticate the givin request
++        Attempt to authenticate the giving request
+ 
+         @param request: An L{IRequest} to be authenticated.
+         """
+         authHeader = request.headers.getHeader('authorization')
+ 
+         if authHeader is None:
+-            return self.portal.login(credentials.Anonymous(),
+-                                     None,
+-                                     *self.interfaces
+-                                     ).addCallbacks(self._loginSucceeded,
+-                                                    self._loginFailed,
+-                                                    (request,), None,
+-                                                    (request,), None)
++            try:
++                avatar = (yield self.portal.login(credentials.Anonymous(), None, *self.interfaces))
++            except:
++                yield self._loginFailed(request)
++            resource = self._loginSucceeded(avatar, request)
++            returnValue(resource)
+ 
+         elif authHeader[0] not in self.credentialFactories:
+-            raise http.HTTPError(UnauthorizedResponse(
+-                                    self.credentialFactories,
+-                                    request.remoteAddr))
++            yield self._loginFailed(request)
+         else:
+-            return self.login(self.credentialFactories[authHeader[0]],
+-                              authHeader[1], request)
++            result = (yield self.login(self.credentialFactories[authHeader[0]], authHeader[1], request))
++            returnValue(result)
+ 
+     def locateChild(self, request, seg):
+         """
+         Authenticate the request then return the C{self.wrappedResource}
+         and the unmodified segments.
+         """
+-        return self.authenticate(request), seg
++        d = self.authenticate(request)
++        d.addCallback(lambda result:(result, seg,))
++        return d
+ 
+     def renderHTTP(self, request):
+         """

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2008-12-06 03:56:15 UTC (rev 3470)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/resource.py	(revision 19773)
 +++ twisted/web2/dav/resource.py	(working copy)
-@@ -31,19 +31,28 @@
+@@ -31,20 +31,30 @@
      "DAVResource",
      "DAVLeafResource",
      "DAVPrincipalResource",
@@ -27,12 +27,15 @@
  
  from zope.interface import implements
  from twisted.python import log
+-from twisted.internet.defer import Deferred, maybeDeferred, succeed
 +from twisted.python.failure import Failure
 +from twisted.cred.error import LoginFailed, UnauthorizedLogin
- from twisted.internet.defer import Deferred, maybeDeferred, succeed
++from twisted.internet.defer import Deferred, maybeDeferred, succeed,\
++    inlineCallbacks, returnValue
  from twisted.internet.defer import waitForDeferred, deferredGenerator
  from twisted.internet import reactor
-@@ -52,12 +61,13 @@
+ from twisted.web2 import responsecode
+@@ -52,12 +62,13 @@
  from twisted.web2.http_headers import generateContentType
  from twisted.web2.iweb import IResponse
  from twisted.web2.resource import LeafResource
@@ -47,7 +50,7 @@
  from twisted.web2.dav.http import NeedPrivilegesResponse
  from twisted.web2.dav.noneprops import NonePropertyStore
  from twisted.web2.dav.util import unimplemented, parentForURL, joinURL
-@@ -126,10 +136,13 @@
+@@ -126,10 +137,13 @@
         #(dav_namespace, "group"                     ), # RFC 3744, section 5.2
          (dav_namespace, "supported-privilege-set"   ), # RFC 3744, section 5.3
          (dav_namespace, "current-user-privilege-set"), # RFC 3744, section 5.4
@@ -61,7 +64,7 @@
  
          (twisted_dav_namespace, "resource-class"),
      )
-@@ -166,6 +179,14 @@
+@@ -166,6 +180,14 @@
          if qname[0] == twisted_private_namespace:
              return succeed(False)
  
@@ -76,7 +79,7 @@
          return succeed(qname in self.liveProperties or self.deadProperties().contains(qname))
  
      def readProperty(self, property, request):
-@@ -201,7 +222,6 @@
+@@ -201,7 +223,6 @@
                      mimeType = self.contentType()
                      if mimeType is None:
                          return None
@@ -84,7 +87,7 @@
                      return davxml.GETContentType(generateContentType(mimeType))
  
                  if name == "getcontentlength":
-@@ -239,8 +259,10 @@
+@@ -239,8 +260,10 @@
                      )
  
                  if name == "supported-report-set":
@@ -97,7 +100,7 @@
  
                  if name == "supported-privilege-set":
                      return self.supportedPrivileges(request)
-@@ -252,9 +274,10 @@
+@@ -252,9 +275,10 @@
                      return davxml.InheritedACLSet(*self.inheritedACLSet())
  
                  if name == "principal-collection-set":
@@ -111,7 +114,7 @@
  
                  def ifAllowed(privileges, callback):
                      def onError(failure):
-@@ -286,7 +309,36 @@
+@@ -286,7 +310,36 @@
                          d.addCallback(gotACL)
                          return d
                      return ifAllowed((davxml.ReadACL(),), callback)
@@ -148,7 +151,7 @@
              elif namespace == twisted_dav_namespace:
                  if name == "resource-class":
                      class ResourceClass (davxml.WebDAVTextElement):
-@@ -309,10 +361,7 @@
+@@ -309,10 +362,7 @@
          """
          See L{IDAVResource.writeProperty}.
          """
@@ -160,7 +163,7 @@
  
          def defer():
              if property.protected:
-@@ -363,15 +412,28 @@
+@@ -363,15 +413,28 @@
          """
          See L{IDAVResource.listProperties}.
          """
@@ -193,7 +196,7 @@
      def listAllprop(self, request):
          """
          Some DAV properties should not be returned to a C{DAV:allprop} query.
-@@ -465,8 +527,22 @@
+@@ -465,8 +528,22 @@
              return super(DAVPropertyMixIn, self).displayName()
  
  class DAVResource (DAVPropertyMixIn, StaticRenderMixin):
@@ -216,7 +219,7 @@
      ##
      # DAV
      ##
-@@ -553,12 +629,13 @@
+@@ -553,69 +630,65 @@
      def supportedReports(self):
          """
          See L{IDAVResource.supportedReports}.
@@ -231,54 +234,79 @@
          return result
  
      ##
-@@ -570,19 +647,21 @@
+     # Authentication
+     ##
+ 
++    @inlineCallbacks
+     def authorize(self, request, privileges, recurse=False):
+         """
          See L{IDAVResource.authorize}.
          """
-         def onError(failure):
+-        def onError(failure):
 -            log.err("Invalid authentication details: %s" % (request,))
 -            raise HTTPError(UnauthorizedResponse(
-+            failure.trap(UnauthorizedLogin, LoginFailed)
 +
-+            log.msg("Authentication failed: %s" % (failure.value,))
-+            return Failure(HTTPError(UnauthorizedResponse(
++        try:
++            yield self.authenticate(request)
++        except (UnauthorizedLogin, LoginFailed), e:
++            log.msg("Authentication failed: %s" % (e,))
++            response = (yield UnauthorizedResponse.makeResponse(
                  request.credentialFactories,
                  request.remoteAddr
--            ))
-+            )))
+             ))
++            raise HTTPError(response)
  
-         def onAuth(result):
-             def onErrors(failure):
-                 failure.trap(AccessDeniedError)
-                 
+-        def onAuth(result):
+-            def onErrors(failure):
+-                failure.trap(AccessDeniedError)
+-                
 -                # If we were unauthorized to start with (no Authorization header from client) then
-+                # If we were unauthenticated to start with (no Authorization header from client) then
-                 # we should return an unauthorized response instead to force the client to login if it can
+-                # we should return an unauthorized response instead to force the client to login if it can
 -                if request.user == davxml.Principal(davxml.Unauthenticated()):
-+                if request.authnUser == davxml.Principal(davxml.Unauthenticated()):
-                     response = UnauthorizedResponse(request.credentialFactories,
-                                                     request.remoteAddr)
-                 else:
-@@ -593,7 +672,7 @@
-                 # class is supposed to be a FORBIDDEN status code and
-                 # "Authorization will not help" according to RFC2616
-                 #
+-                    response = UnauthorizedResponse(request.credentialFactories,
+-                                                    request.remoteAddr)
+-                else:
+-                    response = NeedPrivilegesResponse(request.uri,
+-                                                      failure.value.errors)
+-                #
+-                # We're not adding the headers here because this response
+-                # class is supposed to be a FORBIDDEN status code and
+-                # "Authorization will not help" according to RFC2616
+-                #
 -                raise HTTPError(response)
-+                return Failure(HTTPError(response))
++        try:
++            yield self.checkPrivileges(request, privileges, recurse)
++        except AccessDeniedError, e:
++            # If we were unauthenticated to start with (no Authorization header from client) then
++            # we should return an unauthorized response instead to force the client to login if it can
++            if request.authnUser == davxml.Principal(davxml.Unauthenticated()):
++                response = (yield UnauthorizedResponse.makeResponse(
++                    request.credentialFactories,
++                    request.remoteAddr
++                ))
++            else:
++                response = NeedPrivilegesResponse(request.uri, e.errors)
++            #
++            # We're not adding the headers here because this response
++            # class is supposed to be a FORBIDDEN status code and
++            # "Authorization will not help" according to RFC2616
++            #
++            raise HTTPError(response)
  
-             d = self.checkPrivileges(request, privileges, recurse)
-             d.addErrback(onErrors)
-@@ -606,16 +685,21 @@
- 
+-            d = self.checkPrivileges(request, privileges, recurse)
+-            d.addErrback(onErrors)
+-            return d
+-
+-        d = maybeDeferred(self.authenticate, request)
+-        d.addCallbacks(onAuth, onError)
+-
+-        return d
+-
++    @inlineCallbacks
      def authenticate(self, request):
-         def loginSuccess(result):
+-        def loginSuccess(result):
 -            request.user = result[1]
 -            return request.user
-+            """
-+            @param result: returned tuple from auth.DAVRealm.requestAvatar.
-+            """
-+            request.authnUser = result[1]
-+            request.authzUser = result[2]
-+            return (request.authnUser, request.authzUser,)
  
          if not (
              hasattr(request, 'portal') and 
@@ -289,45 +317,51 @@
 -            return request.user
 +            request.authnUser = davxml.Principal(davxml.Unauthenticated())
 +            request.authzUser = davxml.Principal(davxml.Unauthenticated())
-+            return (request.authnUser, request.authzUser,)
++            returnValue((request.authnUser, request.authzUser,))
  
          authHeader = request.headers.getHeader('authorization')
  
-@@ -631,9 +715,10 @@
+@@ -627,27 +700,23 @@
+             else:
+                 factory = request.credentialFactories[authHeader[0]]
  
+-                creds = factory.decode(authHeader[1], request)
++                creds = (yield factory.decode(authHeader[1], request))
+ 
                  # Try to match principals in each principal collection on the resource
-                 def gotDetails(details):
+-                def gotDetails(details):
 -                    principal = IDAVPrincipalResource(details[0])
 -                    principalURI = details[1]
 -                    return PrincipalCredentials(principal, principalURI, creds)
-+                    authnPrincipal, authzPrincipal = details
-+                    authnPrincipal = IDAVPrincipalResource(authnPrincipal)
-+                    authzPrincipal = IDAVPrincipalResource(authzPrincipal)
-+                    return PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
++                authnPrincipal, authzPrincipal = (yield self.principalsForAuthID(request, creds.username))
++                authnPrincipal = IDAVPrincipalResource(authnPrincipal)
++                authzPrincipal = IDAVPrincipalResource(authzPrincipal)
  
-                 def login(pcreds):
-                     d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -641,13 +726,15 @@
+-                def login(pcreds):
+-                    d = request.portal.login(pcreds, None, *request.loginInterfaces)
+-                    d.addCallback(loginSuccess)
++                pcreds = PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
  
-                     return d
- 
+-                    return d
+-
 -                d = self.findPrincipalForAuthID(request, creds.username)
 -                d.addCallback(gotDetails).addCallback(login)
-+                d = self.principalsForAuthID(request, creds.username)
-+                d.addCallback(gotDetails)
-+                d.addCallback(login)
- 
-                 return d
+-
+-                return d
++                result = (yield request.portal.login(pcreds, None, *request.loginInterfaces))
++                request.authnUser = result[1]
++                request.authzUser = result[2]
++                returnValue((request.authnUser, request.authzUser,))
          else:
 -            request.user = davxml.Principal(davxml.Unauthenticated())
 -            return request.user
 +            request.authnUser = davxml.Principal(davxml.Unauthenticated())
 +            request.authzUser = davxml.Principal(davxml.Unauthenticated())
-+            return (request.authnUser, request.authzUser,)
++            returnValue((request.authnUser, request.authzUser,))
  
      ##
      # ACL
-@@ -656,49 +743,23 @@
+@@ -656,49 +725,23 @@
      def currentPrincipal(self, request):
          """
          @param request: the request being processed.
@@ -386,7 +420,7 @@
          """
          @return: the L{davxml.ACL} element containing the default access control
              list for this resource.
-@@ -710,6 +771,17 @@
+@@ -710,6 +753,17 @@
          #
          return readonlyACL
  
@@ -404,7 +438,7 @@
      def setAccessControlList(self, acl):
          """
          See L{IDAVResource.setAccessControlList}.
-@@ -748,13 +820,16 @@
+@@ -748,13 +802,16 @@
          # 10. Verify that new acl is not in conflict with itself
          # 11. Update acl on the resource
  
@@ -422,7 +456,7 @@
  
          # Need to get list of supported privileges
          supported = []
-@@ -773,10 +848,7 @@
+@@ -773,10 +830,7 @@
          yield supportedPrivs
          supportedPrivs = supportedPrivs.getResult()
          for item in supportedPrivs.children:
@@ -434,7 +468,7 @@
              addSupportedPrivilege(item)
  
          # Steps 1 - 6
-@@ -910,8 +982,7 @@
+@@ -910,8 +964,7 @@
          supportedPrivs = supportedPrivs.getResult()
  
          # Other principals types don't make sense as actors.
@@ -444,7 +478,7 @@
              "Principal is not an actor: %r" % (principal,)
          )
  
-@@ -1019,15 +1090,16 @@
+@@ -1019,15 +1072,16 @@
          def getMyURL():
              url = request.urlForResource(self)
  
@@ -464,7 +498,7 @@
                  "Expected %s response from readDeadProperty() exception, not %s"
                  % (responsecode.NOT_FOUND, e.response.code)
              )
-@@ -1038,9 +1110,9 @@
+@@ -1038,9 +1092,9 @@
  
              if myURL == "/":
                  # If we get to the root without any ACLs, then use the default.
@@ -476,7 +510,7 @@
  
          # Dynamically update privileges for those ace's that are inherited.
          if inheritance:
-@@ -1076,7 +1148,7 @@
+@@ -1076,7 +1130,7 @@
                                  # Adjust ACE for inherit on this resource
                                  children = list(ace.children)
                                  children.remove(TwistedACLInheritable())
@@ -485,7 +519,7 @@
                                  aces.append(davxml.ACE(*children))
              else:
                  aces.extend(inherited_aces)
-@@ -1105,8 +1177,7 @@
+@@ -1105,8 +1159,7 @@
          the child resource loop and supply those to the checkPrivileges on each child.
  
          @param request: the L{IRequest} for the request in progress.
@@ -495,7 +529,7 @@
          """
          
          # Get the parent ACLs with inheritance and preserve the <inheritable> element.
-@@ -1128,21 +1199,9 @@
+@@ -1128,21 +1181,9 @@
                  # Adjust ACE for inherit on this resource
                  children = list(ace.children)
                  children.remove(TwistedACLInheritable())
@@ -519,7 +553,7 @@
  
      inheritedACEsforChildren = deferredGenerator(inheritedACEsforChildren)
  
-@@ -1152,49 +1211,69 @@
+@@ -1152,49 +1193,69 @@
  
          This implementation returns an empty set.
          """
@@ -617,7 +651,7 @@
      def samePrincipal(self, principal1, principal2):
          """
          Check whether the two prinicpals are exactly the same in terms of
-@@ -1219,7 +1298,6 @@
+@@ -1219,7 +1280,6 @@
              return False
                  
      def matchPrincipal(self, principal1, principal2, request):
@@ -625,7 +659,7 @@
          """
          Check whether the principal1 is a principal in the set defined by
          principal2.
-@@ -1244,6 +1322,9 @@
+@@ -1244,6 +1304,9 @@
              if isinstance(principal1, davxml.Unauthenticated):
                  yield False
                  return
@@ -635,7 +669,7 @@
              else:
                  yield True
                  return
-@@ -1260,10 +1341,7 @@
+@@ -1260,10 +1323,7 @@
              yield False
              return
  
@@ -647,7 +681,7 @@
  
          principal2 = waitForDeferred(self.resolvePrincipal(principal2, request))
          yield principal2
-@@ -1271,7 +1349,6 @@
+@@ -1271,7 +1331,6 @@
  
          assert principal2 is not None, "principal2 is None"
  
@@ -655,7 +689,7 @@
          # Compare two HRefs and do group membership test as well
          if principal1 == principal2:
              yield True
-@@ -1289,6 +1366,7 @@
+@@ -1289,6 +1348,7 @@
  
      matchPrincipal = deferredGenerator(matchPrincipal)
  
@@ -663,7 +697,7 @@
      def principalIsGroupMember(self, principal1, principal2, request):
          """
          Check whether one principal is a group member of another.
-@@ -1299,18 +1377,21 @@
+@@ -1299,18 +1359,21 @@
          @return: L{Deferred} with result C{True} if principal1 is a member of principal2, C{False} otherwise
          """
          
@@ -696,7 +730,7 @@
          
      def validPrincipal(self, ace_principal, request):
          """
-@@ -1351,11 +1432,16 @@
+@@ -1351,11 +1414,16 @@
          @return C{True} if C{href_principal} is valid, C{False} otherwise.
  
          This implementation tests for a href element that corresponds to
@@ -716,7 +750,7 @@
          return d
  
      def resolvePrincipal(self, principal, request):
-@@ -1404,8 +1490,7 @@
+@@ -1404,8 +1472,7 @@
              try:
                  principal = principal.getResult()
              except HTTPError, e:
@@ -726,7 +760,7 @@
                      "Expected %s response from readProperty() exception, not %s"
                      % (responsecode.NOT_FOUND, e.response.code)
                  )
-@@ -1432,15 +1517,15 @@
+@@ -1432,15 +1499,15 @@
                  log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
                  yield None
                  return
@@ -745,7 +779,7 @@
              "Not a meta-principal: %r" % (principal,)
          )
  
-@@ -1517,6 +1602,270 @@
+@@ -1517,6 +1584,270 @@
          return None
  
      ##
@@ -1016,7 +1050,7 @@
      # HTTP
      ##
  
-@@ -1525,15 +1874,11 @@
+@@ -1525,15 +1856,11 @@
          #litmus = request.headers.getRawHeaders("x-litmus")
          #if litmus: log.msg("*** Litmus test: %s ***" % (litmus,))
  
@@ -1034,7 +1068,7 @@
  
          def setHeaders(response):
              response = IResponse(response)
-@@ -1567,7 +1912,7 @@
+@@ -1567,7 +1894,7 @@
      def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
          return succeed(None)
  
@@ -1043,7 +1077,7 @@
      """
      Resource representing a WebDAV principal.  (RFC 3744, section 2)
      """
-@@ -1577,7 +1922,7 @@
+@@ -1577,7 +1904,7 @@
      # WebDAV
      ##
  
@@ -1052,7 +1086,7 @@
          (dav_namespace, "alternate-URI-set"),
          (dav_namespace, "principal-URL"    ),
          (dav_namespace, "group-member-set" ),
-@@ -1585,14 +1930,11 @@
+@@ -1585,14 +1912,11 @@
      )
  
      def davComplianceClasses(self):
@@ -1068,7 +1102,7 @@
      def readProperty(self, property, request):
          def defer():
              if type(property) is tuple:
-@@ -1610,10 +1952,20 @@
+@@ -1610,10 +1934,20 @@
                      return davxml.PrincipalURL(davxml.HRef(self.principalURL()))
  
                  if name == "group-member-set":
@@ -1091,7 +1125,7 @@
  
                  if name == "resourcetype":
                      if self.isCollection():
-@@ -1655,7 +2007,7 @@
+@@ -1655,7 +1989,7 @@
          principals.  Subclasses should override this method to provide member
          URLs for this resource if appropriate.
          """
@@ -1100,7 +1134,7 @@
  
      def groupMemberships(self):
          """
-@@ -1666,6 +2018,7 @@
+@@ -1666,6 +2000,7 @@
          """
          unimplemented(self)
  
@@ -1108,7 +1142,7 @@
      def principalMatch(self, href):
          """
          Check whether the supplied principal matches this principal or is a
-@@ -1675,10 +2028,33 @@
+@@ -1675,10 +2010,33 @@
          """
          uri = str(href)
          if self.principalURL() == uri:
@@ -1144,7 +1178,7 @@
  class AccessDeniedError(Exception):
      def __init__(self, errors):
          """ 
-@@ -1718,6 +2094,37 @@
+@@ -1718,6 +2076,37 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch (from rev 3469, CalendarServer/branches/users/cdaboo/digestdb-fix-3445/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch	2008-12-06 03:56:15 UTC (rev 3470)
@@ -0,0 +1,243 @@
+Index: twisted/web2/test/test_httpauth.py
+===================================================================
+--- twisted/web2/test/test_httpauth.py	(revision 19773)
++++ twisted/web2/test/test_httpauth.py	(working copy)
+@@ -1,3 +1,4 @@
++from twisted.internet.defer import inlineCallbacks
+ import md5
+ from twisted.internet import address
+ from twisted.trial import unittest
+@@ -41,22 +42,25 @@
+         self.username = 'dreid'
+         self.password = 'S3CuR1Ty'
+ 
++    @inlineCallbacks
+     def testUsernamePassword(self):
+         response = base64.encodestring('%s:%s' % (
+                 self.username,
+                 self.password))
+ 
+-        creds = self.credentialFactory.decode(response, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(response, _trivial_GET))
+         self.failUnless(creds.checkPassword(self.password))
+ 
++    @inlineCallbacks
+     def testIncorrectPassword(self):
+         response = base64.encodestring('%s:%s' % (
+                 self.username,
+                 'incorrectPassword'))
+ 
+-        creds = self.credentialFactory.decode(response, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(response, _trivial_GET))
+         self.failIf(creds.checkPassword(self.password))
+ 
++    @inlineCallbacks
+     def testIncorrectPadding(self):
+         response = base64.encodestring('%s:%s' % (
+                 self.username,
+@@ -64,7 +68,7 @@
+ 
+         response = response.strip('=')
+ 
+-        creds = self.credentialFactory.decode(response, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(response, _trivial_GET))
+         self.failUnless(creds.checkPassword(self.password))
+ 
+     def testInvalidCredentials(self):
+@@ -135,6 +139,7 @@
+             )
+         return expected
+ 
++    @inlineCallbacks
+     def test_getChallenge(self):
+         """
+         Test that all the required fields exist in the challenge,
+@@ -142,42 +147,44 @@
+         DigestCredentialFactory
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+         self.assertEquals(challenge['qop'], 'auth')
+         self.assertEquals(challenge['realm'], 'test realm')
+         self.assertEquals(challenge['algorithm'], 'md5')
+         self.assertTrue(challenge.has_key("nonce"))
+         self.assertTrue(challenge.has_key("opaque"))
+ 
++    @inlineCallbacks
+     def test_response(self):
+         """
+         Test that we can decode a valid response to our challenge
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+         self.failUnless(creds.checkPassword('password'))
+ 
++    @inlineCallbacks
+     def test_multiResponse(self):
+         """
+         Test that multiple responses to to a single challenge are handled
+         successfully.
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+         self.failUnless(creds.checkPassword('password'))
+ 
+         clientResponse = authRequest2 % (
+@@ -185,24 +192,25 @@
+             self.getDigestResponse(challenge, "00000002"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+         self.failUnless(creds.checkPassword('password'))
+ 
++    @inlineCallbacks
+     def test_failsWithDifferentMethod(self):
+         """
+         Test that the response fails if made for a different request method
+         than it is being issued for.
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse,
+-                                              SimpleRequest(None, 'POST', '/'))
++        creds = (yield self.credentialFactory.decode(clientResponse,
++                                              SimpleRequest(None, 'POST', '/')))
+         self.failIf(creds.checkPassword('password'))
+ 
+     def test_noUsername(self):
+@@ -247,20 +255,21 @@
+                               _trivial_GET)
+         self.assertEquals(str(e), "Invalid response, no opaque given.")
+ 
++    @inlineCallbacks
+     def test_checkHash(self):
+         """
+         Check that given a hash of the form 'username:realm:password'
+         we can verify the digest challenge
+         """
+ 
+-        challenge = self.credentialFactory.getChallenge(clientAddress)
++        challenge = (yield self.credentialFactory.getChallenge(clientAddress))
+ 
+         clientResponse = authRequest1 % (
+             challenge['nonce'],
+             self.getDigestResponse(challenge, "00000001"),
+             challenge['opaque'])
+ 
+-        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++        creds = (yield self.credentialFactory.decode(clientResponse, _trivial_GET))
+ 
+         self.failUnless(creds.checkHash(
+                 md5.md5('username:test realm:password').hexdigest()))
+@@ -268,6 +277,7 @@
+         self.failIf(creds.checkHash(
+                 md5.md5('username:test realm:bogus').hexdigest()))
+ 
++    @inlineCallbacks
+     def test_invalidOpaque(self):
+         """
+         Test that login fails when the opaque does not contain all the required
+@@ -276,7 +286,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         self.assertRaises(
+             error.LoginFailed,
+@@ -302,6 +312,7 @@
+             challenge['nonce'],
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_incompatibleNonce(self):
+         """
+         Test that login fails when the given nonce from the response, does not
+@@ -310,7 +321,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         badNonceOpaque = credentialFactory.generateOpaque(
+             '1234567890',
+@@ -330,6 +341,7 @@
+             '',
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_incompatibleClientIp(self):
+         """
+         Test that the login fails when the request comes from a client ip
+@@ -338,7 +350,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         badNonceOpaque = credentialFactory.generateOpaque(
+             challenge['nonce'],
+@@ -351,6 +363,7 @@
+             challenge['nonce'],
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_oldNonce(self):
+         """
+         Test that the login fails when the given opaque is older than
+@@ -359,7 +372,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+         key = '%s,%s,%s' % (challenge['nonce'],
+                             clientAddress.host,
+@@ -376,6 +389,7 @@
+             challenge['nonce'],
+             clientAddress.host)
+ 
++    @inlineCallbacks
+     def test_mismatchedOpaqueChecksum(self):
+         """
+         Test that login fails when the opaque checksum fails verification
+@@ -383,7 +397,7 @@
+ 
+         credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
+ 
+-        challenge = credentialFactory.getChallenge(clientAddress)
++        challenge = (yield credentialFactory.getChallenge(clientAddress))
+ 
+ 
+         key = '%s,%s,%s' % (challenge['nonce'],

Modified: CalendarServer/trunk/twistedcaldav/authkerb.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/authkerb.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/twistedcaldav/authkerb.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -139,7 +139,7 @@
         super(BasicKerberosCredentialFactory, self).__init__(principal, type, hostname)
 
     def getChallenge(self, _ignore_peer):
-        return {'realm': self.realm}
+        return succeed({'realm': self.realm})
 
     def decode(self, response, request): #@UnusedVariable
         try:
@@ -150,7 +150,7 @@
         creds = creds.split(':', 1)
         if len(creds) == 2:
             c = BasicKerberosCredentials(creds[0], creds[1], self.service, self.realm)
-            return c
+            return succeed(c)
         raise error.LoginFailed('Invalid credentials')
 
 class BasicKerberosCredentialsChecker(LoggingMixIn):
@@ -209,7 +209,7 @@
         super(NegotiateCredentialFactory, self).__init__(principal, type, hostname)
 
     def getChallenge(self, _ignore_peer):
-        return {}
+        return succeed({})
 
     def decode(self, base64data, request):
         
@@ -270,7 +270,7 @@
 
         request.addResponseFilter(responseFilterAddWWWAuthenticate)
 
-        return NegotiateCredentials(username)
+        return succeed(NegotiateCredentials(username))
 
 class NegotiateCredentialsChecker(object):
 

Modified: CalendarServer/trunk/twistedcaldav/directory/digest.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/digest.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/twistedcaldav/directory/digest.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -14,29 +14,22 @@
 # limitations under the License.
 ##
 
-from twistedcaldav.sql import AbstractSQLDatabase
-
 from twisted.cred import error
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.web2.auth.digest import DigestCredentialFactory
 from twisted.web2.auth.digest import DigestedCredentials
-
-from zope.interface import implements, Interface
-
-import cPickle as pickle
-from twisted.web2.http_headers import tokenize
 from twisted.web2.http_headers import Token
-from twisted.web2.http_headers import split
 from twisted.web2.http_headers import parseKeyValue
-import os
-import time
+from twisted.web2.http_headers import split
+from twisted.web2.http_headers import tokenize
 
-try:
-    from sqlite3 import OperationalError
-except ImportError:
-    from pysqlite2.dbapi2 import OperationalError
-
 from twistedcaldav.log import Logger
+from twistedcaldav.memcacher import Memcacher
 
+from zope.interface import implements, Interface
+
+import time
+
 log = Logger()
 
 """
@@ -94,297 +87,40 @@
         """
         pass
 
-    def deleteMany(self, keys):
-        """
-        Remove the records associated with the supplied keys.
+class DigestCredentialsMemcache(Memcacher):
 
-        @param key:        the key to remove.
-        @type key:         C{str}
-        """
-        pass
-
-    def keys(self):
-        """
-        Return all the keys currently available.
-        
-        @return:    a C{list} of C{str} for each key currently in the database.
-        """
-        pass
-    
-    def items(self):
-        """
-        Return all the key/value pairs currently available.
-        
-        @return:    a C{list} of C{tuple} for each key/value currently in the database.
-        """
-        pass
-    
-class DigestCredentialsMap(object):
-
     implements(IDigestCredentialsDatabase)
 
-    def __init__(self, *args):
-        self.db = {}
+    def __init__(self, namespace):
+        super(DigestCredentialsMemcache, self).__init__(
+            namespace=namespace,
+            pickle=True,
+        )
     
     def has_key(self, key):
         """
         See IDigestCredentialsDatabase.
         """
-        return self.db.has_key(key)
+        d = self.get(key)
+        d.addCallback(lambda value:value is not None)
+        return d
 
     def set(self, key, value):
         """
         See IDigestCredentialsDatabase.
         """
-        self.db[key] = value
-
-    def get(self, key):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        if self.db.has_key(key):
-            return self.db[key]
-        else:
-            return None
-
-    def delete(self, key):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        if self.db.has_key(key):
-            del self.db[key]
-
-    def deleteMany(self, keys):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        for key in keys:
-            if self.db.has_key(key):
-                del self.db[key]
-
-    def keys(self):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        return self.db.keys()
-
-    def items(self):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        return self.db.items()
-
-class DigestCredentialsDB(AbstractSQLDatabase):
-
-    implements(IDigestCredentialsDatabase)
-
-    """
-    A database to maintain cached digest credentials.
-
-    SCHEMA:
-    
-    Database: DIGESTCREDENTIALS
-    
-    ROW: KEY, VALUE
-    
-    """
-    
-    dbType = "DIGESTCREDENTIALSCACHE"
-    dbFilename = "digest.sqlite"
-    dbFormatVersion = "2"
-
-    exceptionLimit = 10
-
-    def __init__(self, path):
-        db_path = os.path.join(path, DigestCredentialsDB.dbFilename)
-        super(DigestCredentialsDB, self).__init__(db_path, False, autocommit=False)
-        self.exceptions = 0
-    
-    def has_key(self, key):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        try:
-            for ignore_key in self._db_execute(
-                "select KEY from DIGESTCREDENTIALS where KEY = :1",
-                key
-            ):
-                return True
-            else:
-                return False
-            self.exceptions = 0
-        except OperationalError, e:
-            self.exceptions += 1
-            if self.exceptions >= self.exceptionLimit:
-                self._db_close()
-                log.err("Reset digest credentials database connection: %s" % (e,))
-            raise
-
-    def set(self, key, value):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        try:
-            pvalue = pickle.dumps(value)
-            self._set_in_db(key, pvalue)
-            self._db_commit()
-            self.exceptions = 0
-        except OperationalError, e:
-            self._db_rollback()
-            self.exceptions += 1
-            if self.exceptions >= self.exceptionLimit:
-                self._db_close()
-                log.err("Reset digest credentials database connection: %s" % (e,))
-            raise
-
-    def get(self, key):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        try:
-            for pvalue in self._db_execute(
-                "select VALUE from DIGESTCREDENTIALS where KEY = :1",
-                key
-            ):
-                self.exceptions = 0
-                return pickle.loads(str(pvalue[0]))
-            else:
-                self.exceptions = 0
-                return None
-        except OperationalError, e:
-            self.exceptions += 1
-            if self.exceptions >= self.exceptionLimit:
-                self._db_close()
-                log.err("Reset digest credentials database connection: %s" % (e,))
-            raise
-
-    def delete(self, key):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        try:
-            self._delete_from_db(key)
-            self._db_commit()
-            self.exceptions = 0
-        except OperationalError, e:
-            self._db_rollback()
-            self.exceptions += 1
-            if self.exceptions >= self.exceptionLimit:
-                self._db_close()
-                log.err("Reset digest credentials database connection: %s" % (e,))
-            raise
-
-    def deleteMany(self, keys):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        try:
-            for key in keys:
-                self._delete_from_db(key)
-            self._db_commit()
-            self.exceptions = 0
-        except OperationalError, e:
-            self._db_rollback()
-            self.exceptions += 1
-            if self.exceptions >= self.exceptionLimit:
-                self._db_close()
-                log.err("Reset digest credentials database connection: %s" % (e,))
-            raise
-
-    def keys(self):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        try:
-            result = []
-            for key in self._db_execute("select KEY from DIGESTCREDENTIALS"):
-                result.append(str(key[0]))
-            
-            self.exceptions = 0
-            return result
-        except OperationalError, e:
-            self.exceptions += 1
-            if self.exceptions >= self.exceptionLimit:
-                self._db_close()
-                log.err("Reset digest credentials database connection: %s" % (e,))
-            raise
-
-    def items(self):
-        """
-        See IDigestCredentialsDatabase.
-        """
-        try:
-            result = []
-            for key in self._db_execute("select KEY, VALUE from DIGESTCREDENTIALS"):
-                result.append((str(key[0]), pickle.loads(str(key[1])),))
-            
-            self.exceptions = 0
-            return result
-        except OperationalError, e:
-            self.exceptions += 1
-            if self.exceptions >= self.exceptionLimit:
-                self._db_close()
-                log.err("Reset digest credentials database connection: %s" % (e,))
-            raise
-
-    def _set_in_db(self, key, value):
-        """
-        Insert the specified entry into the database, replacing any that might already exist.
-
-        @param key:   the key to add.
-        @param value: the value to add.
-        """
-        self._db().execute(
-            """
-            insert or replace into DIGESTCREDENTIALS (KEY, VALUE)
-            values (:1, :2)
-            """, (key, value,)
+        super(DigestCredentialsMemcache, self).set(
+            key,
+            value,
+            expire_time=DigestCredentialFactory.CHALLENGE_LIFETIME_SECS
         )
-       
-    def _delete_from_db(self, key):
-        """
-        Deletes the specified entry from the database.
 
-        @param key: the key to remove.
-        """
-        self._db().execute("delete from DIGESTCREDENTIALS where KEY = :1", (key,))
-    
-    def _db_version(self):
-        """
-        @return: the schema version assigned to this index.
-        """
-        return DigestCredentialsDB.dbFormatVersion
-        
-    def _db_type(self):
-        """
-        @return: the collection type assigned to this index.
-        """
-        return DigestCredentialsDB.dbType
-        
-    def _db_init_data_tables(self, q):
-        """
-        Initialise the underlying database tables.
-        @param q:           a database cursor to use.
-        """
-
-        #
-        # DIGESTCREDENTIALS table
-        #
-        q.execute(
-            """
-            create table DIGESTCREDENTIALS (
-                KEY         text unique,
-                VALUE       text
-            )
-            """
-        )
-
 class QopDigestCredentialFactory(DigestCredentialFactory):
     """
     See twisted.web2.auth.digest.DigestCredentialFactory
     """
 
-    def __init__(self, algorithm, qop, realm, db_path):
+    def __init__(self, algorithm, qop, realm, namespace="DIGESTCREDENTIALS"):
         """
         @type algorithm: C{str}
         @param algorithm: case insensitive string that specifies
@@ -399,17 +135,12 @@
         @type realm: C{str}
         @param realm: case sensitive string that specifies the realm
             portion of the challenge
-
-        @type db_path: C{str}
-        @param db_path: path where the credentials cache is to be stored
         """
         super(QopDigestCredentialFactory, self).__init__(algorithm, realm)
         self.qop = qop
-        self.db = DigestCredentialsDB(db_path)
-        
-        # Always clean-up when we start-up
-        self.cleanup()
+        self.db = DigestCredentialsMemcache(namespace)
 
+    @inlineCallbacks
     def getChallenge(self, peer):
         """
         Generate the challenge for use in the WWW-Authenticate header
@@ -425,16 +156,19 @@
         c = self.generateNonce()
         
         # Make sure it is not a duplicate
-        if self.db.has_key(c):
+        result = (yield self.db.has_key(c))
+        if result:
             raise AssertionError("nonce value already cached in credentials database: %s" % (c,))
 
         # The database record is a tuple of (client ip, nonce-count, timestamp)
-        self.db.set(c, (peer.host, 0, time.time()))
+        yield self.db.set(c, (peer.host, 0, time.time()))
 
-        challenge = {'nonce': c,
-                     'qop': 'auth',
-                     'algorithm': self.algorithm,
-                     'realm': self.realm}
+        challenge = {
+            'nonce': c,
+            'qop': 'auth',
+            'algorithm': self.algorithm,
+            'realm': self.realm,
+        }
 
         if self.qop:
             challenge['qop'] = self.qop
@@ -445,9 +179,9 @@
         if hasattr(peer, 'stale') and peer.stale:
             challenge['stale'] = 'true'
 
-        return challenge
-            
+        returnValue(challenge)
 
+    @inlineCallbacks
     def decode(self, response, request):
         """
         Do the default behavior but then strip out any 'qop' from the credential fields
@@ -490,19 +224,20 @@
             raise error.LoginFailed('Invalid response, no nonce given.')
 
         # Now verify the nonce/cnonce values for this client
-        if self.validate(auth, request):
-
+        result = (yield self._validate(auth, request))
+        if result:
             credentials = DigestedCredentials(username,
                                        request.method,
                                        self.realm,
                                        auth)
             if not self.qop and credentials.fields.has_key('qop'):
                 del credentials.fields['qop']
-            return credentials
+            returnValue(credentials)
         else:
             raise error.LoginFailed('Invalid nonce/cnonce values')
 
-    def validate(self, auth, request):
+    @inlineCallbacks
+    def _validate(self, auth, request):
         """
         Check that the parameters in the response represent a valid set of credentials that
         may be being re-used.
@@ -521,73 +256,56 @@
         nonce_count = auth.get('nc')
 
         # First check we have this nonce
-        if not self.db.has_key(nonce):
+        result = (yield self.db.get(nonce))
+        if result is None:
             raise error.LoginFailed('Invalid nonce value: %s' % (nonce,))
-        db_clientip, db_nonce_count, db_timestamp = self.db.get(nonce)
+        db_clientip, db_nonce_count, db_timestamp = result
 
         # Next check client ip
         if db_clientip != clientip:
-            self.invalidate(nonce)
+            yield self._invalidate(nonce)
             raise error.LoginFailed('Client IPs do not match: %s and %s' % (clientip, db_clientip,))
         
         # cnonce and nonce-count MUST be present if qop is present
         if auth.get('qop') is not None:
             if auth.get('cnonce') is None:
-                self.invalidate(nonce)
+                yield self._invalidate(nonce)
                 raise error.LoginFailed('cnonce is required when qop is specified')
             if nonce_count is None:
-                self.invalidate(nonce)
+                yield self._invalidate(nonce)
                 raise error.LoginFailed('nonce-count is required when qop is specified')
                 
             # Next check the nonce-count is one greater than the previous one and update it in the DB
             try:
                 nonce_count = int(nonce_count, 16)
             except ValueError:
-                self.invalidate(nonce)
+                yield self._invalidate(nonce)
                 raise error.LoginFailed('nonce-count is not a valid hex string: %s' % (auth.get('nonce-count'),))            
             if nonce_count != db_nonce_count + 1:
-                self.invalidate(nonce)
+                yield self._invalidate(nonce)
                 raise error.LoginFailed('nonce-count value out of sequence: %s should be one more than %s' % (nonce_count, db_nonce_count,))
-            self.db.set(nonce, (db_clientip, nonce_count, db_timestamp))
+            yield self.db.set(nonce, (db_clientip, nonce_count, db_timestamp))
         else:
             # When not using qop the stored nonce-count must always be zero.
             # i.e. we can't allow a qop auth then a non-qop auth with the same nonce
             if db_nonce_count != 0:
-                self.invalidate(nonce)
+                yield self._invalidate(nonce)
                 raise error.LoginFailed('nonce-count was sent with this nonce: %s' % (nonce,))                
         
         # Now check timestamp
         if db_timestamp + DigestCredentialFactory.CHALLENGE_LIFETIME_SECS <= time.time():
-            self.invalidate(nonce)
+            yield self._invalidate(nonce)
             if request.remoteAddr:
                 request.remoteAddr.stale = True
             raise error.LoginFailed('Digest credentials expired')
 
-        return True
+        returnValue(True)
     
-    def invalidate(self, nonce):
+    def _invalidate(self, nonce):
         """
         Invalidate cached credentials for the specified nonce value.
 
-        @param nonce:    the nonce for the record to invalidate.
+        @param nonce:    the nonce for the record to _invalidate.
         @type nonce:     C{str}
         """
-        self.db.delete(nonce)
-
-    def cleanup(self):
-        """
-        This should be called at regular intervals to remove expired credentials from the cache.
-        """
-        items = self.db.items()
-        oldest_allowed = time.time() - DigestCredentialFactory.CHALLENGE_LIFETIME_SECS
-        delete_keys = []
-        for key, value in items:
-            ignore_clientip, ignore_cnonce, db_timestamp = value
-            if db_timestamp <= oldest_allowed:
-                delete_keys.append(key)
-
-        try:
-            self.db.deleteMany(delete_keys)
-        except Exception, e:
-            # Clean-up errors can be logged but we should ignore them
-            log.err("Failed to clean digest credentials: %s" % (e,))
+        return self.db.delete(nonce)

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_digest.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -1,15 +1,33 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
 
-
 from twisted.cred import error
 from twisted.internet import address
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python import failure
 from twisted.web2.auth import digest
 from twisted.web2.auth.wrapper import UnauthorizedResponse
 from twisted.web2.test.test_server import SimpleRequest
-from twisted.web2.dav.fileop import rmdir
+
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.test.util import TestCase
-import os
+from twistedcaldav.config import config
+
 import md5
+import sys
 
 class FakeDigestCredentialFactory(QopDigestCredentialFactory):
     """
@@ -81,28 +99,24 @@
         Create a DigestCredentialFactory for testing
         """
         TestCase.setUp(self)
-        self.path1 = self.mktemp()
-        self.path2 = self.mktemp()
-        os.mkdir(self.path1)
-        os.mkdir(self.path2)
+        config.ProcessType = "Single"
 
+        self.namespace1 = "DIGEST1"
+        self.namespace2 = "DIGEST2"
+
         self.credentialFactories = (QopDigestCredentialFactory(
                                           'md5',
                                           'auth',
                                           'test realm',
-                                          self.path1
+                                          self.namespace1
                                       ),
                                       QopDigestCredentialFactory(
                                           'md5',
                                           '',
                                           'test realm',
-                                          self.path2
+                                          self.namespace2
                                       ))
 
-    def tearDown(self):
-        rmdir(self.path1)
-        rmdir(self.path2)
-
     def getDigestResponse(self, challenge, ncount):
         """
         Calculate the response for the given challenge
@@ -163,6 +177,22 @@
                 )
         return expected
 
+    @inlineCallbacks
+    def assertRaisesDeferred(self, exception, f, *args, **kwargs):
+        try:
+            result = (yield f(*args, **kwargs))
+        except exception, inst:
+            returnValue(inst)
+        except:
+            raise self.failureException('%s raised instead of %s:\n %s'
+                                        % (sys.exc_info()[0],
+                                           exception.__name__,
+                                           failure.Failure().getTraceback()))
+        else:
+            raise self.failureException('%s not raised (%r returned)'
+                                        % (exception.__name__, result))
+
+    @inlineCallbacks
     def test_getChallenge(self):
         """
         Test that all the required fields exist in the challenge,
@@ -170,34 +200,36 @@
         DigestCredentialFactory
         """
 
-        challenge = self.credentialFactories[0].getChallenge(clientAddress)
+        challenge = (yield self.credentialFactories[0].getChallenge(clientAddress))
         self.assertEquals(challenge['qop'], 'auth')
         self.assertEquals(challenge['realm'], 'test realm')
         self.assertEquals(challenge['algorithm'], 'md5')
         self.assertTrue(challenge.has_key("nonce"))
 
-        challenge = self.credentialFactories[1].getChallenge(clientAddress)
+        challenge = (yield self.credentialFactories[1].getChallenge(clientAddress))
         self.assertFalse(challenge.has_key('qop'))
         self.assertEquals(challenge['realm'], 'test realm')
         self.assertEquals(challenge['algorithm'], 'md5')
         self.assertTrue(challenge.has_key("nonce"))
 
+    @inlineCallbacks
     def test_response(self):
         """
         Test that we can decode a valid response to our challenge
         """
 
         for ctr, factory in enumerate(self.credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
+            challenge = (yield factory.getChallenge(clientAddress))
     
             clientResponse = authRequest1[ctr] % (
                 challenge['nonce'],
                 self.getDigestResponse(challenge, "00000001"),
             )
     
-            creds = factory.decode(clientResponse, _trivial_GET())
+            creds = (yield factory.decode(clientResponse, _trivial_GET()))
             self.failUnless(creds.checkPassword('password'))
 
+    @inlineCallbacks
     def test_multiResponse(self):
         """
         Test that multiple responses to to a single challenge are handled
@@ -205,14 +237,14 @@
         """
 
         for ctr, factory in enumerate(self.credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
+            challenge = (yield factory.getChallenge(clientAddress))
     
             clientResponse = authRequest1[ctr] % (
                 challenge['nonce'],
                 self.getDigestResponse(challenge, "00000001"),
             )
     
-            creds = factory.decode(clientResponse, _trivial_GET())
+            creds = (yield factory.decode(clientResponse, _trivial_GET()))
             self.failUnless(creds.checkPassword('password'))
     
             clientResponse = authRequest2[ctr] % (
@@ -220,9 +252,10 @@
                 self.getDigestResponse(challenge, "00000002"),
             )
     
-            creds = factory.decode(clientResponse, _trivial_GET())
+            creds = (yield factory.decode(clientResponse, _trivial_GET()))
             self.failUnless(creds.checkPassword('password'))
 
+    @inlineCallbacks
     def test_failsWithDifferentMethod(self):
         """
         Test that the response fails if made for a different request method
@@ -230,17 +263,18 @@
         """
 
         for ctr, factory in enumerate(self.credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
+            challenge = (yield factory.getChallenge(clientAddress))
     
             clientResponse = authRequest1[ctr] % (
                 challenge['nonce'],
                 self.getDigestResponse(challenge, "00000001"),
             )
     
-            creds = factory.decode(clientResponse,
-                                                  SimpleRequest(None, 'POST', '/'))
+            creds = (yield factory.decode(clientResponse,
+                                                  SimpleRequest(None, 'POST', '/')))
             self.failIf(creds.checkPassword('password'))
 
+    @inlineCallbacks
     def test_noUsername(self):
         """
         Test that login fails when our response does not contain a username,
@@ -249,31 +283,33 @@
 
         # Check for no username
         for factory in self.credentialFactories:
-            e = self.assertRaises(error.LoginFailed,
+            e = (yield self.assertRaisesDeferred(error.LoginFailed,
                                   factory.decode,
                                   namelessAuthRequest,
-                                  _trivial_GET())
+                                  _trivial_GET()))
             self.assertEquals(str(e), "Invalid response, no username given.")
     
             # Check for an empty username
-            e = self.assertRaises(error.LoginFailed,
+            e = (yield self.assertRaisesDeferred(error.LoginFailed,
                                   factory.decode,
                                   namelessAuthRequest + ',username=""',
-                                  _trivial_GET())
+                                  _trivial_GET()))
             self.assertEquals(str(e), "Invalid response, no username given.")
 
+    @inlineCallbacks
     def test_noNonce(self):
         """
         Test that login fails when our response does not contain a nonce
         """
 
         for factory in self.credentialFactories:
-            e = self.assertRaises(error.LoginFailed,
+            e = (yield self.assertRaisesDeferred(error.LoginFailed,
                                   factory.decode,
                                   'realm="Test",username="Foo",opaque="bar"',
-                                  _trivial_GET())
+                                  _trivial_GET()))
             self.assertEquals(str(e), "Invalid response, no nonce given.")
 
+    @inlineCallbacks
     def test_emptyAttribute(self):
         """
         Test that login fails when our response contains an attribute
@@ -282,12 +318,13 @@
 
         # Check for no username
         for factory in self.credentialFactories:
-            e = self.assertRaises(error.LoginFailed,
+            e = (yield self.assertRaisesDeferred(error.LoginFailed,
                                   factory.decode,
                                   emtpyAttributeAuthRequest,
-                                  _trivial_GET())
+                                  _trivial_GET()))
             self.assertEquals(str(e), "Invalid response, no username given.")
 
+    @inlineCallbacks
     def test_checkHash(self):
         """
         Check that given a hash of the form 'username:realm:password'
@@ -295,14 +332,14 @@
         """
 
         for ctr, factory in enumerate(self.credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
+            challenge = (yield factory.getChallenge(clientAddress))
     
             clientResponse = authRequest1[ctr] % (
                 challenge['nonce'],
                 self.getDigestResponse(challenge, "00000001"),
             )
     
-            creds = factory.decode(clientResponse, _trivial_GET())
+            creds = (yield factory.decode(clientResponse, _trivial_GET()))
     
             self.failUnless(creds.checkHash(
                     md5.md5('username:test realm:password').hexdigest()))
@@ -310,18 +347,19 @@
             self.failIf(creds.checkHash(
                     md5.md5('username:test realm:bogus').hexdigest()))
 
+    @inlineCallbacks
     def test_invalidNonceCount(self):
         """
         Test that login fails when the nonce-count is repeated.
         """
 
         credentialFactories = (
-            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.path1),
-            FakeDigestCredentialFactory('md5', '', 'test realm', self.path2)
+            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.namespace1),
+            FakeDigestCredentialFactory('md5', '', 'test realm', self.namespace2)
         )
 
         for ctr, factory in enumerate(credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
+            challenge = (yield factory.getChallenge(clientAddress))
     
             clientResponse1 = authRequest1[ctr] % (
                 challenge['nonce'],
@@ -333,18 +371,18 @@
                 self.getDigestResponse(challenge, "00000002"),
             )
     
-            factory.decode(clientResponse1, _trivial_GET())
-            factory.decode(clientResponse2, _trivial_GET())
+            yield factory.decode(clientResponse1, _trivial_GET())
+            yield factory.decode(clientResponse2, _trivial_GET())
     
             if challenge.get('qop') is not None:
-                self.assertRaises(
+                yield self.assertRaisesDeferred(
                     error.LoginFailed,
                     factory.decode,
                     clientResponse2,
                     _trivial_GET()
                 )
                 
-                challenge = factory.getChallenge(clientAddress)
+                challenge = (yield factory.getChallenge(clientAddress))
 
                 clientResponse1 = authRequest1[ctr] % (
                     challenge['nonce'],
@@ -355,14 +393,15 @@
                     challenge['nonce'],
                     self.getDigestResponse(challenge, "00000002"),
                 )
-                factory.decode(clientResponse1, _trivial_GET())
-                self.assertRaises(
+                yield factory.decode(clientResponse1, _trivial_GET())
+                yield self.assertRaisesDeferred(
                     error.LoginFailed,
                     factory.decode,
                     clientResponse3,
                     _trivial_GET()
                 )
 
+    @inlineCallbacks
     def test_invalidNonce(self):
         """
         Test that login fails when the given nonce from the response, does not
@@ -370,12 +409,12 @@
         """
 
         credentialFactories = (
-            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.path1),
-            FakeDigestCredentialFactory('md5', '', 'test realm', self.path2)
+            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.namespace1),
+            FakeDigestCredentialFactory('md5', '', 'test realm', self.namespace2)
         )
 
         for ctr, factory in enumerate(credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
+            challenge = (yield factory.getChallenge(clientAddress))
             challenge['nonce'] = "noNoncense"
     
             clientResponse = authRequest1[ctr] % (
@@ -384,17 +423,21 @@
             )
     
             request = _trivial_GET()
-            self.assertRaises(
+            yield self.assertRaisesDeferred(
                 error.LoginFailed,
                 factory.decode,
                 clientResponse,
                 request
             )
 
-            factory.invalidate(factory.generateNonce())
-            response = UnauthorizedResponse({"Digest":factory}, request.remoteAddr)
+            factory._invalidate(factory.generateNonce())
+            response = (yield UnauthorizedResponse.makeResponse(
+                {"Digest":factory},
+                request.remoteAddr
+            ))
             response.headers.getHeader("www-authenticate")[0][1]
 
+    @inlineCallbacks
     def test_incompatibleClientIp(self):
         """
         Test that the login fails when the request comes from a client ip
@@ -402,12 +445,12 @@
         """
 
         credentialFactories = (
-            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.path1),
-            FakeDigestCredentialFactory('md5', '', 'test realm', self.path2)
+            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.namespace1),
+            FakeDigestCredentialFactory('md5', '', 'test realm', self.namespace2)
         )
 
         for ctr, factory in enumerate(credentialFactories):
-            challenge = factory.getChallenge(address.IPv4Address('TCP', '127.0.0.2', 80))
+            challenge = (yield factory.getChallenge(address.IPv4Address('TCP', '127.0.0.2', 80)))
     
             clientResponse = authRequest1[ctr] % (
                 challenge['nonce'],
@@ -415,17 +458,21 @@
             )
     
             request = _trivial_GET()
-            self.assertRaises(
+            yield self.assertRaisesDeferred(
                 error.LoginFailed,
                 factory.decode,
                 clientResponse,
                 request
             )
 
-            response = UnauthorizedResponse({"Digest":factory}, request.remoteAddr)
+            response = (yield UnauthorizedResponse.makeResponse(
+                {"Digest":factory},
+                request.remoteAddr,
+            ))
             wwwhdrs = response.headers.getHeader("www-authenticate")[0][1]
             self.assertTrue('stale' not in wwwhdrs, msg="Stale parameter in Digest WWW-Authenticate headers: %s" % (wwwhdrs,))
 
+    @inlineCallbacks
     def test_oldNonce(self):
         """
         Test that the login fails when the given opaque is older than
@@ -433,13 +480,13 @@
         """
 
         credentialFactories = (
-            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.path1),
-            FakeDigestCredentialFactory('md5', '', 'test realm', self.path2)
+            FakeDigestCredentialFactory('md5', 'auth', 'test realm', self.namespace1),
+            FakeDigestCredentialFactory('md5', '', 'test realm', self.namespace2)
         )
 
         for ctr, factory in enumerate(credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
-            clientip, nonce_count, timestamp = factory.db.get(challenge['nonce'])
+            challenge = (yield factory.getChallenge(clientAddress))
+            clientip, nonce_count, timestamp = (yield factory.db.get(challenge['nonce']))
             factory.db.set(challenge['nonce'], (clientip, nonce_count, timestamp - 2 * digest.DigestCredentialFactory.CHALLENGE_LIFETIME_SECS))
     
             clientResponse = authRequest1[ctr] % (
@@ -448,14 +495,17 @@
             )
     
             request = _trivial_GET()
-            self.assertRaises(
+            yield self.assertRaisesDeferred(
                 error.LoginFailed,
                 factory.decode,
                 clientResponse,
                 request
             )
             
-            response = UnauthorizedResponse({"Digest":factory}, request.remoteAddr)
+            response = (yield UnauthorizedResponse.makeResponse(
+                {"Digest":factory},
+                request.remoteAddr,
+            ))
             wwwhdrs = response.headers.getHeader("www-authenticate")[0][1]
             self.assertTrue('stale' in wwwhdrs, msg="No stale parameter in Digest WWW-Authenticate headers: %s" % (wwwhdrs,))
             self.assertEquals(wwwhdrs['stale'], 'true', msg="stale parameter not set to true in Digest WWW-Authenticate headers: %s" % (wwwhdrs,))
@@ -486,20 +536,21 @@
                 preHA1=preHA1
                 )
 
+    @inlineCallbacks
     def test_commaURI(self):
         """
         Check that commas in valued are parsed out properly.
         """
 
         for ctr, factory in enumerate(self.credentialFactories):
-            challenge = factory.getChallenge(clientAddress)
+            challenge = (yield factory.getChallenge(clientAddress))
     
             clientResponse = authRequestComma[ctr] % (
                 challenge['nonce'],
                 self.getDigestResponseComma(challenge, "00000001"),
             )
     
-            creds = factory.decode(clientResponse, _trivial_GET())
+            creds = (yield factory.decode(clientResponse, _trivial_GET()))
             self.failUnless(creds.checkPassword('password'))
 
 

Modified: CalendarServer/trunk/twistedcaldav/directory/wiki.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/wiki.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/twistedcaldav/directory/wiki.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -207,12 +207,11 @@
 
             if userID == "unauthenticated":
                 # Return a 401 so they have an opportunity to log in
-                raise HTTPError(
-                    UnauthorizedResponse(
-                        request.credentialFactories,
-                        request.remoteAddr
-                    )
-                )
+                response = (yield UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr,
+                ))
+                raise HTTPError(response)
 
             raise HTTPError(
                 StatusResponse(

Modified: CalendarServer/trunk/twistedcaldav/root.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/root.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/twistedcaldav/root.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -19,8 +19,8 @@
     "RootResource",
 ]
 
-from twisted.internet.defer import maybeDeferred, succeed
-from twisted.python.failure import Failure
+from twisted.internet.defer import succeed, inlineCallbacks,\
+    returnValue
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
 
 from twisted.web2 import responsecode
@@ -46,7 +46,7 @@
         return config.RootResourceACL
 
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
-        # Permissions here are fixed, and are not subject to inherritance rules, etc.
+        # Permissions here are fixed, and are not subject to inheritance rules, etc.
         return succeed(self.defaultAccessControlList())
 
 
@@ -99,98 +99,59 @@
         return self._dead_properties
 
 
+    @inlineCallbacks
     def checkSacl(self, request):
         """
         Check SACLs against the current request
         """
 
-        def _authCb((authnUser, authzUser)):
-            # Ensure that the user is not unauthenticated.
-            # SACLs are authorization for the use of the service,
-            # so unauthenticated access doesn't make any sense.
-            if authzUser == davxml.Principal(davxml.Unauthenticated()):
-                log.msg("Unauthenticated users not enabled with the '%s' SACL" % (self.saclService,))
-                return Failure(HTTPError(UnauthorizedResponse(
-                            request.credentialFactories,
-                            request.remoteAddr)))
+        try:
+            authnUser, authzUser = yield self.authenticate(request)
+        except Exception:
+            response = (yield UnauthorizedResponse.makeResponse(
+                request.credentialFactories,
+                request.remoteAddr
+            ))
+            raise HTTPError(response)
 
-            return (authnUser, authzUser)
+        # Ensure that the user is not unauthenticated.
+        # SACLs are authorization for the use of the service,
+        # so unauthenticated access doesn't make any sense.
+        if authzUser == davxml.Principal(davxml.Unauthenticated()):
+            log.msg("Unauthenticated users not enabled with the '%s' SACL" % (self.saclService,))
+            response = (yield UnauthorizedResponse.makeResponse(
+                request.credentialFactories,
+                request.remoteAddr
+            ))
+            raise HTTPError(response)
 
-        def _authEb(failure):
-            # Make sure we propogate UnauthorizedLogin errors.
-            failure.trap(UnauthorizedLogin, LoginFailed)
+        # Cache the authentication details
+        request.authnUser = authnUser
+        request.authzUser = authzUser
 
-            return Failure(HTTPError(UnauthorizedResponse(
-                        request.credentialFactories,
-                        request.remoteAddr)))
+        # Figure out the "username" from the davxml.Principal object
+        request.checkingSACL = True
+        principal = (yield request.locateResource(authzUser.children[0].children[0].data))
+        delattr(request, "checkingSACL")
+        username = principal.record.shortName
 
-        def _checkSACLCb((authnUser, authzUser)):
-            # Cache the authentication details
-            request.authnUser = authnUser
-            request.authzUser = authzUser
+        if RootResource.CheckSACL(username, self.saclService) != 0:
+            log.msg("User '%s' is not enabled with the '%s' SACL" % (username, self.saclService,))
+            raise HTTPError(responsecode.FORBIDDEN)
 
-            # Figure out the "username" from the davxml.Principal object
-            request.checkingSACL = True
-            d = request.locateResource(authzUser.children[0].children[0].data)
+        # Mark SACL's as having been checked so we can avoid doing it multiple times
+        request.checkedSACL = True
 
-            def _checkedSACLCb(principal):
-                delattr(request, "checkingSACL")
-                username = principal.record.shortName
 
-                if RootResource.CheckSACL(username, self.saclService) != 0:
-                    log.msg("User '%s' is not enabled with the '%s' SACL" % (username, self.saclService,))
-                    return Failure(HTTPError(403))
+        returnValue(True)
 
-                # Mark SACL's as having been checked so we can avoid doing it multiple times
-                request.checkedSACL = True
-                return True
-
-            d.addCallback(_checkedSACLCb)
-            return d
-
-        d = maybeDeferred(self.authenticate, request)
-        d.addCallbacks(_authCb, _authEb)
-        d.addCallback(_checkSACLCb)
-        return d
-
-
+    @inlineCallbacks
     def locateChild(self, request, segments):
-        def _authCb((authnUser, authzUser)):
-            request.authnUser = authnUser
-            request.authzUser = authzUser
 
-        def _authEb(failure):
-            # Make sure we propogate UnauthorizedLogin errors.
-            failure.trap(UnauthorizedLogin, LoginFailed)
-
-            return Failure(HTTPError(UnauthorizedResponse(
-                        request.credentialFactories,
-                        request.remoteAddr)))
-
         for filter in self.contentFilters:
             request.addResponseFilter(filter[0], atEnd=filter[1])
 
-
         # Examine cookies for wiki auth token
-
-        def validSessionID(username):
-            log.info("Wiki lookup returned user: %s" % (username,))
-            directory = request.site.resource.getDirectory()
-            record = directory.recordWithShortName("users", username)
-            if record is None:
-                raise HTTPError(StatusResponse(
-                    responsecode.FORBIDDEN,
-                    "The username (%s) corresponding to your sessionID was not found by calendar server." % (username,)
-                ))
-            request.authnUser = request.authzUser = davxml.Principal(
-                davxml.HRef.fromString("/principals/__uids__/%s/" % (record.guid,)))
-        def invalidSessionID(error):
-            log.info("Wiki lookup returned ERROR: %s" % (error,))
-            raise HTTPError(StatusResponse(
-                responsecode.FORBIDDEN,
-                "Your sessionID was rejected by the authenticating wiki server."
-            ))
-
         wikiConfig = config.Authentication.Wiki
         cookies = request.headers.getHeader('cookie')
         if wikiConfig["Enabled"] and cookies is not None:
@@ -204,20 +165,34 @@
             if token is not None and token != "unauthenticated":
                 log.info("Wiki sessionID cookie value: %s" % (token,))
                 proxy = Proxy(wikiConfig["URL"])
-                d = proxy.callRemote(wikiConfig["UserMethod"],
-                    token).addCallbacks(validSessionID, invalidSessionID)
-                d.addCallback(lambda _: super(RootResource, self
-                                              ).locateChild(request, segments))
-                return d
+                try:
+                    username = (yield proxy.callRemote(wikiConfig["UserMethod"], token))
+                    log.info("Wiki lookup returned user: %s" % (username,))
+                    directory = request.site.resource.getDirectory()
+                    record = directory.recordWithShortName("users", username)
+                    if record is None:
+                        raise HTTPError(StatusResponse(
+                            responsecode.FORBIDDEN,
+                            "The username (%s) corresponding to your sessionID was not found by calendar server." % (username,)
+                        ))
+                    request.authnUser = request.authzUser = davxml.Principal(
+                        davxml.HRef.fromString("/principals/__uids__/%s/" % (record.guid,)))
+                    child = (yield super(RootResource, self).locateChild(request, segments))
+                    returnValue(child)
 
+                except Exception, e:
+                    log.info("Wiki lookup returned ERROR: %s" % (e,))
+                    raise HTTPError(StatusResponse(
+                        responsecode.FORBIDDEN,
+                        "Your sessionID was rejected by the authenticating wiki server."
+                    ))
 
+
         if self.useSacls and not hasattr(request, "checkedSACL") and not hasattr(request, "checkingSACL"):
-            d = self.checkSacl(request)
-            d.addCallback(lambda _: super(RootResource, self
-                                          ).locateChild(request, segments))
+            yield self.checkSacl(request)
+            child = (yield super(RootResource, self).locateChild(request, segments))
+            returnValue(child)
 
-            return d
-
         if config.RejectClients:
             #
             # Filter out unsupported clients
@@ -232,36 +207,33 @@
                             "Your client software (%s) is not allowed to access this service." % (agent,)
                         ))
 
-        def _getCachedResource(_ign, request):
-            if not getattr(request, 'checkingCache', False):
-                request.checkingCache = True
-                d = self.responseCache.getResponseForRequest(request)
-                d.addCallback(_serveResponse)
-                return d
+        if request.method == 'PROPFIND' and not getattr(request, 'notInCache', False):
+            try:
+                authnUser, authzUser = (yield self.authenticate(request))
+                request.authnUser = authnUser
+                request.authzUser = authzUser
+            except (UnauthorizedLogin, LoginFailed):
+                response = (yield UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr
+                ))
+                raise HTTPError(response)
 
-            return super(RootResource, self).locateChild(request, segments)
+            try:
+                if not getattr(request, 'checkingCache', False):
+                    request.checkingCache = True
+                    response = (yield self.responseCache.getResponseForRequest(request))
+                    if response is None:
+                        request.notInCache = True
+                        raise KeyError("Not found in cache.")
+        
+                    returnValue((_CachedResponseResource(response), []))
+            except KeyError:
+                pass
 
-        def _serveResponse(response):
-            if response is None:
-                request.notInCache = True
-                raise KeyError("Not found in cache.")
+        child = (yield super(RootResource, self).locateChild(request, segments))
+        returnValue(child)
 
-            return _CachedResponseResource(response), []
-
-        def _resourceNotInCacheEb(failure):
-            failure.trap(KeyError)
-            return super(RootResource, self).locateChild(request,segments)
-
-        if request.method == 'PROPFIND' and not getattr(
-            request, 'notInCache', False):
-            d = maybeDeferred(self.authenticate, request)
-            d.addCallbacks(_authCb, _authEb)
-            d.addCallback(_getCachedResource, request)
-            d.addErrback(_resourceNotInCacheEb)
-            return d
-
-        return super(RootResource, self).locateChild(request, segments)
-
     def http_COPY       (self, request): return responsecode.FORBIDDEN
     def http_MOVE       (self, request): return responsecode.FORBIDDEN
     def http_DELETE     (self, request): return responsecode.FORBIDDEN

Modified: CalendarServer/trunk/twistedcaldav/test/test_kerberos.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_kerberos.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/twistedcaldav/test/test_kerberos.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -13,9 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+
 from twisted.cred.error import LoginFailed
-
 from twisted.cred.error import UnauthorizedLogin
+from twisted.internet.defer import inlineCallbacks
 from twisted.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import authkerb
@@ -31,10 +32,11 @@
     def test_BasicKerberosCredentials(self):
         authkerb.BasicKerberosCredentials("test", "test", "http/example.com at EXAMPLE.COM", "EXAMPLE.COM")
 
+    @inlineCallbacks
     def test_BasicKerberosCredentialFactory(self):
         factory = authkerb.BasicKerberosCredentialFactory(principal="http/server.example.com at EXAMPLE.COM")
 
-        challenge = factory.getChallenge("peer")
+        challenge = (yield factory.getChallenge("peer"))
         expected_challenge = {'realm': "EXAMPLE.COM"}
         self.assertTrue(challenge == expected_challenge,
                         msg="BasicKerberosCredentialFactory challenge %s != %s" % (challenge, expected_challenge))
@@ -49,17 +51,18 @@
     def test_NegotiateCredentials(self):
         authkerb.NegotiateCredentials("test")
 
+    @inlineCallbacks
     def test_NegotiateCredentialFactory(self):
         factory = authkerb.NegotiateCredentialFactory(principal="http/server.example.com at EXAMPLE.COM")
 
-        challenge = factory.getChallenge("peer")
+        challenge = (yield factory.getChallenge("peer"))
         expected_challenge = {}
         self.assertTrue(challenge == expected_challenge,
                         msg="NegotiateCredentialFactory challenge %s != %s" % (challenge, expected_challenge))
 
         request = SimpleRequest(self.site, "GET", "/")
         try:
-            factory.decode("Bogus Data".encode("base64"), request)
+            yield factory.decode("Bogus Data".encode("base64"), request)
         except (UnauthorizedLogin, LoginFailed):
             pass
         except Exception, ex:

Modified: CalendarServer/trunk/twistedcaldav/test/test_root.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_root.py	2008-12-05 21:38:23 UTC (rev 3469)
+++ CalendarServer/trunk/twistedcaldav/test/test_root.py	2008-12-06 03:56:15 UTC (rev 3470)
@@ -24,7 +24,7 @@
 
 from twisted.cred.portal import Portal
 
-from twisted.internet import defer
+from twisted.internet.defer import inlineCallbacks, maybeDeferred
 
 from twisted.web2 import http_headers
 from twisted.web2 import responsecode
@@ -78,6 +78,7 @@
 
         self.site = server.Site(self.root)
 
+    @inlineCallbacks
     def test_noSacls(self):
         """
         Test the behaviour of locateChild when SACLs are not enabled.
@@ -90,10 +91,9 @@
                                 "GET",
                                 "/principals/")
 
-        resrc, segments = self.root.locateChild(request,
-                                         ['principals'])
+        resrc, segments = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
 
-        resrc, segments = resrc.locateChild(request, ['principals'])
+        resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
 
         self.failUnless(
             isinstance(resrc, DirectoryPrincipalProvisioningResource),
@@ -102,6 +102,7 @@
 
         self.assertEquals(segments, [])
 
+    @inlineCallbacks
     def test_inSacls(self):
         """
         Test the behavior of locateChild when SACLs are enabled and the
@@ -119,26 +120,22 @@
                     'Authorization': ['basic', '%s' % (
                             'dreid:dierd'.encode('base64'),)]}))
 
-        resrc, segments = self.root.locateChild(request,
-                                         ['principals'])
+        resrc, segments = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
 
-        def _Cb((resrc, segments)):
-            self.failUnless(
-                isinstance(resrc, DirectoryPrincipalProvisioningResource),
-                "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,)
-            )
+        resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
 
-            self.assertEquals(segments, [])
+        self.failUnless(
+            isinstance(resrc, DirectoryPrincipalProvisioningResource),
+            "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,)
+        )
 
-            self.assertEquals(request.authzUser,
-                              davxml.Principal(
-                    davxml.HRef('/principals/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/')))
+        self.assertEquals(segments, [])
 
-        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
-        d.addCallback(_Cb)
+        self.assertEquals(request.authzUser,
+                          davxml.Principal(
+                davxml.HRef('/principals/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/')))
 
-        return d
-
+    @inlineCallbacks
     def test_notInSacls(self):
         """
         Test the behavior of locateChild when SACLs are enabled and the
@@ -156,17 +153,14 @@
                     'Authorization': ['basic', '%s' % (
                             'wsanchez:zehcnasw'.encode('base64'),)]}))
 
-        resrc, segments = self.root.locateChild(request, ['principals'])
+        resrc, segments = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
 
-        def _Eb(failure):
-            failure.trap(HTTPError)
-            self.assertEquals(failure.value.response.code, 403)
+        try:
+            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+        except HTTPError, e:
+            self.assertEquals(e.response.code, 403)
 
-        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
-        d.addErrback(_Eb)
-
-        return d
-
+    @inlineCallbacks
     def test_unauthenticated(self):
         """
         Test the behavior of locateChild when SACLs are enabled and the request
@@ -180,24 +174,15 @@
                                 "GET",
                                 "/principals/")
 
-        resrc, segments = self.root.locateChild(request,
-                                                ['principals'])
+        resrc, segments = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
 
-        def _Cb(result):
-            raise AssertionError(("RootResource.locateChild did not return "
-                                  "an error"))
+        try:
+            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+            raise AssertionError(("RootResource.locateChild did not return an error"))
+        except HTTPError, e:
+            self.assertEquals(e.response.code, 401)
 
-        def _Eb(failure):
-            failure.trap(HTTPError)
-            self.assertEquals(failure.value.response.code, 401)
-
-        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
-
-        d.addCallback(_Cb)
-        d.addErrback(_Eb)
-
-        return d
-
+    @inlineCallbacks
     def test_badCredentials(self):
         """
         Test the behavior of locateChild when SACLS are enabled, and
@@ -215,18 +200,13 @@
                     'Authorization': ['basic', '%s' % (
                             'dreid:dreid'.encode('base64'),)]}))
 
-        resrc, segments = self.root.locateChild(request,
-                                         ['principals'])
+        resrc, segments = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
 
-        def _Eb(failure):
-            failure.trap(HTTPError)
-            self.assertEquals(failure.value.response.code, 401)
+        try:
+            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+        except HTTPError, e:
+            self.assertEquals(e.response.code, 401)
 
-        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
-        d.addErrback(_Eb)
-
-        return d
-
     def test_DELETE(self):
         def do_test(response):
             response = IResponse(response)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081205/4b823b3a/attachment-0001.html>


More information about the calendarserver-changes mailing list