[CalendarServer-changes] [1326] CalendarServer/branches/users/dreid/new-twisted-2

source_changes at macosforge.org source_changes at macosforge.org
Tue Mar 6 10:03:59 PST 2007


Revision: 1326
          http://trac.macosforge.org/projects/calendarserver/changeset/1326
Author:   dreid at apple.com
Date:     2007-03-06 10:03:59 -0800 (Tue, 06 Mar 2007)

Log Message:
-----------
Merge forward

Modified Paths:
--------------
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.python.procutils.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.runner.procmon.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.auth.digest.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.__init__.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.auth.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.davxml.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.base.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.parser.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.idav.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.static.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.stream.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.iweb.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.log.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.server.patch
    CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch
    CalendarServer/branches/users/dreid/new-twisted-2/run

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.python.procutils.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.python.procutils.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.python.procutils.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/python/procutils.py
 ===================================================================
---- twisted/python/procutils.py	(revision 18545)
+--- twisted/python/procutils.py	(revision 19725)
 +++ twisted/python/procutils.py	(working copy)
 @@ -33,7 +33,7 @@
      """

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.runner.procmon.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.runner.procmon.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.runner.procmon.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/runner/procmon.py
 ===================================================================
---- twisted/runner/procmon.py	(revision 18545)
+--- twisted/runner/procmon.py	(revision 19725)
 +++ twisted/runner/procmon.py	(working copy)
 @@ -130,10 +130,10 @@
          self.consistency = reactor.callLater(self.consistencyDelay,

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.auth.digest.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.auth.digest.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.auth.digest.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,17 +1,153 @@
 Index: twisted/web2/auth/digest.py
 ===================================================================
---- twisted/web2/auth/digest.py	(revision 18545)
+--- twisted/web2/auth/digest.py	(revision 19737)
 +++ twisted/web2/auth/digest.py	(working copy)
-@@ -5,6 +5,7 @@
+@@ -5,9 +5,10 @@
  
  http://www.faqs.org/rfcs/rfc2617.html
  """
 +import time
  
  from twisted.cred import credentials, error
- from zope.interface import implements
-@@ -118,10 +119,12 @@
- class DigestCredentialFactory:
+-from zope.interface import implements
++from zope.interface import implements, Interface
+ 
+ from twisted.web2.auth.interfaces import ICredentialFactory
+ 
+@@ -17,9 +18,9 @@
+ # The digest math
+ 
+ algorithms = {
+-    'md5': md5.md5,
+-    'md5-sess': md5.md5,
+-    'sha': sha.sha,
++    'md5': md5.new,
++    'md5-sess': md5.new,
++    'sha': sha.new,
+ }
+ 
+ # DigestCalcHA1
+@@ -30,15 +31,40 @@
+     pszPassword,
+     pszNonce,
+     pszCNonce,
++    preHA1=None
+ ):
++    """
++    @param pszAlg: The name of the algorithm to use to calculate the digest.
++        Currently supported are md5 md5-sess and sha.
+ 
+-    m = algorithms[pszAlg]()
+-    m.update(pszUserName)
+-    m.update(":")
+-    m.update(pszRealm)
+-    m.update(":")
+-    m.update(pszPassword)
+-    HA1 = m.digest()
++    @param pszUserName: The username
++    @param pszRealm: The realm
++    @param pszPassword: The password
++    @param pszNonce: The nonce
++    @param pszCNonce: The cnonce
++
++    @param preHA1: If available this is a str containing a previously
++       calculated HA1 as a hex string. If this is given then the values for
++       pszUserName, pszRealm, and pszPassword are ignored.
++    """
++
++    if (preHA1 and (pszUserName or pszRealm or pszPassword)):
++        raise TypeError(("preHA1 is incompatible with the pszUserName, "
++                         "pszRealm, and pszPassword arguments"))
++
++    if not preHA1:
++        # We need to calculate the HA1 from the username:realm:password
++        m = algorithms[pszAlg]()
++        m.update(pszUserName)
++        m.update(":")
++        m.update(pszRealm)
++        m.update(":")
++        m.update(pszPassword)
++        HA1 = m.digest()
++    else:
++        # We were given a username:realm:password
++        HA1 = preHA1.decode('hex')
++
+     if pszAlg == "md5-sess":
+         m = algorithms[pszAlg]()
+         m.update(HA1)
+@@ -47,6 +73,7 @@
+         m.update(":")
+         m.update(pszCNonce)
+         HA1 = m.digest()
++
+     return HA1.encode('hex')
+ 
+ # DigestCalcResponse
+@@ -83,14 +110,29 @@
+         m.update(pszQop)
+         m.update(":")
+     m.update(HA2)
+-    hash = m.digest().encode('hex')
+-    return hash
++    respHash = m.digest().encode('hex')
++    return respHash
+ 
+ 
++class IUsernameDigestHash(Interface):
++    """
++    This credential is used when a CredentialChecker has access to the hash
++    of the username:realm:password as in an Apache .htdigest file.
++    """
++    def checkHash(self, digestHash):
++        """
++        @param digestHash: The hashed username:realm:password to check against.
++
++        @return: a deferred which becomes, or a boolean indicating if the
++            hash matches.
++        """
++
++
+ class DigestedCredentials:
+     """Yet Another Simple HTTP Digest authentication scheme"""
+ 
+-    implements(credentials.IUsernameHashedPassword)
++    implements(credentials.IUsernameHashedPassword,
++               IUsernameDigestHash)
+ 
+     def __init__(self, username, method, realm, fields):
+         self.username = username
+@@ -114,40 +156,143 @@
+ 
+         return expected == response
+ 
++    def checkHash(self, digestHash):
++        response = self.fields.get('response')
++        uri = self.fields.get('uri')
++        nonce = self.fields.get('nonce')
++        cnonce = self.fields.get('cnonce')
++        nc = self.fields.get('nc')
++        algo = self.fields.get('algorithm', 'md5').lower()
++        qop = self.fields.get('qop', 'auth')
+ 
+-class DigestCredentialFactory:
++        expected = calcResponse(
++            calcHA1(algo, None, None, None, nonce, cnonce, preHA1=digestHash),
++            algo, nonce, nc, cnonce, qop, self.method, uri, None
++        )
++
++        return expected == response
++
++
++class DigestCredentialFactory(object):
++    """
++    Support for RFC2617 HTTP Digest Authentication
++
++    @cvar CHALLENGE_LIFETIME_SECS: The number of seconds for which an
++        opaque should be valid.
++
++    @ivar privateKey: A random string used for generating the secure opaque.
++    """
++
      implements(ICredentialFactory)
  
 -    CHALLENGE_LIFETIME = 15
@@ -19,57 +155,117 @@
  
      scheme = "digest"
  
-+    pkey = '%d%d%d' %  tuple([random.randrange(sys.maxint) for _ in range(3)])
-+
      def __init__(self, algorithm, realm):
-         """@type algorithm: C{str}
-            @param algorithm: case insensitive string that specifies
-@@ -132,7 +135,6 @@
-            @param realm: case sensitive string that specifies the realm
-                          portion of the challenge
+-        """@type algorithm: C{str}
+-           @param algorithm: case insensitive string that specifies
+-              the hash algorithm used, should be either, md5, md5-sess
+-              or sha
++        """
++        @type algorithm: C{str}
++        @param algorithm: case insensitive string that specifies
++            the hash algorithm used, should be either, md5, md5-sess
++            or sha
+ 
+-           @type realm: C{str}
+-           @param realm: case sensitive string that specifies the realm
+-                         portion of the challenge
++        @type realm: C{str}
++        @param realm: case sensitive string that specifies the realm
++            portion of the challenge
          """
 -        self.outstanding = {}
          self.algorithm = algorithm
          self.realm = realm
  
-@@ -141,13 +143,41 @@
++        c = tuple([random.randrange(sys.maxint) for _ in range(3)])
++
++        self.privateKey = '%d%d%d' % c
++
+     def generateNonce(self):
+         c = tuple([random.randrange(sys.maxint) for _ in range(3)])
          c = '%d%d%d' % c
          return c
  
 -    def generateOpaque(self):
 -        return str(random.randrange(sys.maxint))
++    def _getTime(self):
++        """
++        Parameterize the time based seed used in generateOpaque
++        so we can deterministically unittest it's behavior.
++        """
++        return time.time()
+ 
 +    def generateOpaque(self, nonce, clientip):
-+        # 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(time.time())))
-+        digest = md5.new(key + DigestCredentialFactory.pkey).hexdigest()
++        """
++        Generate an opaque to be returned to the client.
++        This should be a unique string that can be returned to us and verified.
++        """
++
++        # 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())))
++        digest = md5.new(key + self.privateKey).hexdigest()
 +        ekey = key.encode('base64')
-+        return "%s-%s" % (digest, ekey.replace('\n', ''),)
- 
++        return "%s-%s" % (digest, ekey.strip('\n'))
++
 +    def verifyOpaque(self, opaque, nonce, clientip):
++        """
++        Given the opaque and nonce from the request, as well as the clientip
++        that made the request, verify that the opaque was generated by us.
++        And that it's not too old."
++
++        @param opaque: The opaque value from the Digest response
++        @param nonce: The nonce value from the Digest response
++        @param clientip: The remote IP address of the client making the request
++
++        @return: Return True if the opaque was successfully verified
++
++        @raise L{twisted.cred.error.LoginFailed}
++        """
++
 +        # First split the digest from the key
 +        opaque_parts = opaque.split('-')
 +        if len(opaque_parts) != 2:
 +            raise error.LoginFailed('Invalid response, invalid opaque value')
-+        
++
 +        # Verify the key
 +        key = opaque_parts[1].decode('base64')
 +        key_parts = key.split(',')
++
 +        if len(key_parts) != 3:
 +            raise error.LoginFailed('Invalid response, invalid opaque value')
++
 +        if key_parts[0] != nonce:
-+            raise error.LoginFailed('Invalid response, incompatible opaque/nonce values')
++            raise error.LoginFailed(
++                'Invalid response, incompatible opaque/nonce values')
++
 +        if key_parts[1] != clientip:
-+            raise error.LoginFailed('Invalid response, incompatible opaque/client values')
-+        if int(time.time()) - int(key_parts[2]) > DigestCredentialFactory.CHALLENGE_LIFETIME_SECS:
-+            raise error.LoginFailed('Invalid response, incompatible opaque/nonce too old')
++            raise error.LoginFailed(
++                'Invalid response, incompatible opaque/client values')
 +
++        if (int(self._getTime()) - int(key_parts[2]) >
++            DigestCredentialFactory.CHALLENGE_LIFETIME_SECS):
++
++            raise error.LoginFailed(
++                'Invalid response, incompatible opaque/nonce too old')
++
 +        # Verify the digest
-+        digest = md5.new(key + DigestCredentialFactory.pkey).hexdigest()
++        digest = md5.new(key + self.privateKey).hexdigest()
 +        if digest != opaque_parts[0]:
 +            raise error.LoginFailed('Invalid response, invalid opaque value')
-+        
++
++        return True
++
      def getChallenge(self, peer):
++        """
++        Generate the challenge for use in the WWW-Authenticate header
++
++        @param peer: The L{IAddress} of the requesting client.
++
++        @return: The C{dict} that can be used to generate a WWW-Authenticate
++            header.
++        """
++
          c = self.generateNonce()
 -        o = self.generateOpaque()
 -        self.outstanding[o] = c
@@ -78,15 +274,32 @@
          return {'nonce': c,
                  'opaque': o,
                  'qop': 'auth',
-@@ -167,9 +197,7 @@
+@@ -161,15 +306,22 @@
+             return s
+         response = ' '.join(response.splitlines())
+         parts = response.split(',')
+-        auth = dict([(k.strip(), unq(v.strip())) for (k, v) in [p.split('=', 1) for p in parts]])
+ 
++        auth = {}
++
++        for (k, v) in [p.split('=', 1) for p in parts]:
++            auth[k.strip()] = unq(v.strip())
++
+         username = auth.get('username')
          if not username:
              raise error.LoginFailed('Invalid response, no username given')
  
 -        if auth.get('opaque') not in self.outstanding:
 -            raise error.LoginFailed('Invalid response, opaque not outstanding')
++        # Now verify the nonce/opaque values for this client
++        if self.verifyOpaque(auth.get('opaque'),
++                             auth.get('nonce'),
++                             request.remoteAddr.host):
+ 
+-        del self.outstanding[auth['opaque']]
 -
--        del self.outstanding[auth['opaque']]
-+        # Now verify the nonce/opaque values for this client
-+        self.verifyOpaque(auth.get('opaque'), auth.get('nonce'), request.remoteAddr.host)
-             
-         return DigestedCredentials(username, request.method, self.realm, auth)
+-        return DigestedCredentials(username, request.method, self.realm, auth)
++            return DigestedCredentials(username,
++                                       request.method,
++                                       self.realm,
++                                       auth)

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.__init__.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.__init__.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.__init__.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/__init__.py
 ===================================================================
---- twisted/web2/dav/__init__.py	(revision 18545)
+--- twisted/web2/dav/__init__.py	(revision 19725)
 +++ twisted/web2/dav/__init__.py	(working copy)
 @@ -45,6 +45,7 @@
      "noneprops",

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.auth.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.auth.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.auth.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/auth.py
 ===================================================================
---- twisted/web2/dav/auth.py	(revision 18545)
+--- twisted/web2/dav/auth.py	(revision 19725)
 +++ twisted/web2/dav/auth.py	(working copy)
 @@ -40,7 +40,7 @@
  

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.davxml.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.davxml.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.davxml.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/davxml.py
 ===================================================================
---- twisted/web2/dav/davxml.py	(revision 18545)
+--- twisted/web2/dav/davxml.py	(revision 19725)
 +++ twisted/web2/dav/davxml.py	(working copy)
 @@ -45,6 +45,7 @@
  from twisted.web2.dav.element.rfc2518 import *

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/element/__init__.py
 ===================================================================
---- twisted/web2/dav/element/__init__.py	(revision 18545)
+--- twisted/web2/dav/element/__init__.py	(revision 19725)
 +++ twisted/web2/dav/element/__init__.py	(working copy)
 @@ -35,4 +35,5 @@
      "rfc2518",

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.base.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.base.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.base.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/element/base.py
 ===================================================================
---- twisted/web2/dav/element/base.py	(revision 18545)
+--- twisted/web2/dav/element/base.py	(revision 19725)
 +++ twisted/web2/dav/element/base.py	(working copy)
 @@ -145,21 +145,20 @@
  

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.parser.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.parser.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.parser.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/element/parser.py
 ===================================================================
---- twisted/web2/dav/element/parser.py	(revision 18545)
+--- twisted/web2/dav/element/parser.py	(revision 19725)
 +++ twisted/web2/dav/element/parser.py	(working copy)
 @@ -106,6 +106,26 @@
              "children"   : [],

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/element/rfc4331.py	(revision 0)
 +++ twisted/web2/dav/element/rfc4331.py	(revision 0)
-@@ -0,0 +1,55 @@
+@@ -0,0 +1,110 @@
 +##
 +# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
 +#
@@ -58,3 +58,58 @@
 +    name = "quota-used-bytes"
 +    hidden = True
 +    protected = True
++##
++# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++# DRI: Cyrus Daboo, cdaboo at apple.com
++##
++
++"""
++RFC 4331 (Quota and Size Properties for WebDAV Collections) XML Elements
++
++This module provides XML element definitions for use with WebDAV.
++
++See RFC 4331: http://www.ietf.org/rfc/rfc4331.txt
++"""
++
++from twisted.web2.dav.element.base import WebDAVTextElement
++
++##
++# Section 3 & 4 (Quota Properties)
++##
++
++class QuotaAvailableBytes (WebDAVTextElement):
++    """
++    Property which contains the the number of bytes available under the
++    current quota to store data in a collection (RFC 4331, section 3)
++    """
++    name = "quota-available-bytes"
++    hidden = True
++    protected = True
++
++class QuotaUsedBytes (WebDAVTextElement):
++    """
++    Property which contains the the number of bytes used under the
++    current quota to store data in a collection (RFC 4331, section 4)
++    """
++    name = "quota-used-bytes"
++    hidden = True
++    protected = True

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.idav.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.idav.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.idav.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/idav.py
 ===================================================================
---- twisted/web2/dav/idav.py	(revision 18545)
+--- twisted/web2/dav/idav.py	(revision 19725)
 +++ twisted/web2/dav/idav.py	(working copy)
 @@ -41,7 +41,7 @@
              otherwise.

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/__init__.py
 ===================================================================
---- twisted/web2/dav/method/__init__.py	(revision 18545)
+--- twisted/web2/dav/method/__init__.py	(revision 19725)
 +++ twisted/web2/dav/method/__init__.py	(working copy)
 @@ -40,6 +40,7 @@
      "proppatch",

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/copymove.py
 ===================================================================
---- twisted/web2/dav/method/copymove.py	(revision 18545)
+--- twisted/web2/dav/method/copymove.py	(revision 19725)
 +++ twisted/web2/dav/method/copymove.py	(working copy)
 @@ -34,11 +34,12 @@
  from twisted.python import log

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/delete.py
 ===================================================================
---- twisted/web2/dav/method/delete.py	(revision 18545)
+--- twisted/web2/dav/method/delete.py	(revision 19725)
 +++ twisted/web2/dav/method/delete.py	(working copy)
 @@ -58,8 +58,28 @@
      yield x

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/put.py
 ===================================================================
---- twisted/web2/dav/method/put.py	(revision 18545)
+--- twisted/web2/dav/method/put.py	(revision 19725)
 +++ twisted/web2/dav/method/put.py	(working copy)
 @@ -34,7 +34,7 @@
  from twisted.web2 import responsecode

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/method/put_common.py	(revision 0)
 +++ twisted/web2/dav/method/put_common.py	(revision 0)
-@@ -0,0 +1,265 @@
+@@ -0,0 +1,530 @@
 +##
 +# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
 +#
@@ -268,3 +268,268 @@
 +        raise
 +
 +storeResource = deferredGenerator(storeResource)
++##
++# 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.
++#
++# DRI: Cyrus Daboo, cdaboo at apple.com
++##
++
++"""
++PUT/COPY/MOVE common behavior.
++"""
++
++__version__ = "0.0"
++
++__all__ = ["storeCalendarObjectResource"]
++
++from twisted.internet.defer import deferredGenerator, maybeDeferred, waitForDeferred
++from twisted.python import failure, log
++from twisted.python.filepath import FilePath
++from twisted.web2 import responsecode
++from twisted.web2.dav import davxml
++from twisted.web2.dav.fileop import copy, delete, put
++from twisted.web2.dav.resource import TwistedGETContentMD5
++from twisted.web2.dav.stream import MD5StreamWrapper
++from twisted.web2.http import HTTPError
++from twisted.web2.http import StatusResponse
++from twisted.web2.iweb import IResponse
++from twisted.web2.stream import MemoryStream
++
++import md5
++
++def storeResource(
++    request,
++    source=None, source_uri=None, data=None,
++    destination=None, destination_uri=None,
++    deletesource=False,
++    depth="0"
++):
++    """
++    Function that does common PUT/COPY/MOVE behaviour.
++    
++    @param request:           the L{twisted.web2.server.Request} for the current HTTP request.
++    @param source:            the L{DAVFile} for the source resource to copy from, or None if source data
++                              is to be read from the request.
++    @param source_uri:        the URI for the source resource.
++    @param data:              a C{str} to copy data from instead of the request stream.
++    @param destination:       the L{DAVFile} for the destination resource to copy into.
++    @param destination_uri:   the URI for the destination resource.
++    @param deletesource:      True if the source resource is to be deleted on successful completion, False otherwise.
++    @param depth:             a C{str} containing the COPY/MOVE Depth header value.
++    @return:                  status response.
++    """
++    
++    try:
++        assert request is not None and destination is not None and destination_uri is not None
++        assert (source is None) or (source is not None and source_uri is not None)
++        assert not deletesource or (deletesource and source is not None)
++    except AssertionError:
++        log.err("Invalid arguments to storeResource():")
++        log.err("request=%s\n" % (request,))
++        log.err("source=%s\n" % (source,))
++        log.err("source_uri=%s\n" % (source_uri,))
++        log.err("data=%s\n" % (data,))
++        log.err("destination=%s\n" % (destination,))
++        log.err("destination_uri=%s\n" % (destination_uri,))
++        log.err("deletesource=%s\n" % (deletesource,))
++        log.err("depth=%s\n" % (depth,))
++        raise
++
++    class RollbackState(object):
++        """
++        This class encapsulates the state needed to rollback the entire PUT/COPY/MOVE
++        transaction, leaving the server state the same as it was before the request was
++        processed. The DoRollback method will actually execute the rollback operations.
++        """
++        
++        def __init__(self):
++            self.active = True
++            self.source_copy = None
++            self.destination_copy = None
++            self.destination_created = False
++            self.source_deleted = False
++        
++        def Rollback(self):
++            """
++            Rollback the server state. Do not allow this to raise another exception. If
++            rollback fails then we are going to be left in an awkward state that will need
++            to be cleaned up eventually.
++            """
++            if self.active:
++                self.active = False
++                log.err("Rollback: rollback")
++                try:
++                    if self.source_copy and self.source_deleted:
++                        self.source_copy.moveTo(source.fp)
++                        log.err("Rollback: source restored %s to %s" % (self.source_copy.path, source.fp.path))
++                        self.source_copy = None
++                        self.source_deleted = False
++                    if self.destination_copy:
++                        destination.fp.remove()
++                        log.err("Rollback: destination restored %s to %s" % (self.destination_copy.path, destination.fp.path))
++                        self.destination_copy.moveTo(destination.fp)
++                        self.destination_copy = None
++                    elif self.destination_created:
++                        destination.fp.remove()
++                        log.err("Rollback: destination removed %s" % (destination.fp.path,))
++                        self.destination_created = False
++                except:
++                    log.err("Rollback: exception caught and not handled: %s" % failure.Failure())
++
++        def Commit(self):
++            """
++            Commit the resource changes by wiping the rollback state.
++            """
++            if self.active:
++                log.err("Rollback: commit")
++                self.active = False
++                if self.source_copy:
++                    self.source_copy.remove()
++                    log.err("Rollback: removed source backup %s" % (self.source_copy.path,))
++                    self.source_copy = None
++                if self.destination_copy:
++                    self.destination_copy.remove()
++                    log.err("Rollback: removed destination backup %s" % (self.destination_copy.path,))
++                    self.destination_copy = None
++                self.destination_created = False
++                self.source_deleted = False
++    
++    rollback = RollbackState()
++
++    try:
++        """
++        Handle validation operations here.
++        """
++
++        """
++        Handle rollback setup here.
++        """
++
++        # Do quota checks on destination and source before we start messing with adding other files
++        destquota = waitForDeferred(destination.quota(request))
++        yield destquota
++        destquota = destquota.getResult()
++        if destquota is not None and destination.exists():
++            old_dest_size = waitForDeferred(destination.quotaSize(request))
++            yield old_dest_size
++            old_dest_size = old_dest_size.getResult()
++        else:
++            old_dest_size = 0
++            
++        if source is not None:
++            sourcequota = waitForDeferred(source.quota(request))
++            yield sourcequota
++            sourcequota = sourcequota.getResult()
++            if sourcequota is not None and source.exists():
++                old_source_size = waitForDeferred(source.quotaSize(request))
++                yield old_source_size
++                old_source_size = old_source_size.getResult()
++            else:
++                old_source_size = 0
++        else:
++            sourcequota = None
++            old_source_size = 0
++
++        # We may need to restore the original resource data if the PUT/COPY/MOVE fails,
++        # so rename the original file in case we need to rollback.
++        overwrite = destination.exists()
++        if overwrite:
++            rollback.destination_copy = FilePath(destination.fp.path)
++            rollback.destination_copy.path += ".rollback"
++            destination.fp.copyTo(rollback.destination_copy)
++        else:
++            rollback.destination_created = True
++
++        if deletesource:
++            rollback.source_copy = FilePath(source.fp.path)
++            rollback.source_copy.path += ".rollback"
++            source.fp.copyTo(rollback.source_copy)
++    
++        """
++        Handle actual store operations here.
++        """
++
++        # Do put or copy based on whether source exists
++        if source is not None:
++            response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, depth)
++        else:
++            datastream = request.stream
++            if data is not None:
++                datastream = MemoryStream(data)
++            md5 = MD5StreamWrapper(datastream)
++            response = maybeDeferred(put, md5, destination.fp)
++
++        response = waitForDeferred(response)
++        yield response
++        response = response.getResult()
++
++        # Update the MD5 value on the resource
++        if source is not None:
++            # Copy MD5 value from source to destination
++            if source.hasDeadProperty(TwistedGETContentMD5):
++                md5 = source.readDeadProperty(TwistedGETContentMD5)
++                destination.writeDeadProperty(md5)
++        else:
++            # Finish MD5 calc and write dead property
++            md5.close()
++            md5 = md5.getMD5()
++            destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
++
++        # Update the content-type value on the resource if it is not been copied or moved
++        if source is None:
++            content_type = request.headers.getHeader("content-type")
++            if content_type is not None:
++                destination.writeDeadProperty(davxml.GETContentType.fromString("%s/%s" % (content_type.mediaType, content_type.mediaSubtype,)))
++
++        response = IResponse(response)
++        
++        # Do quota check on destination
++        if destquota is not None:
++            # Get size of new/old resources
++            new_dest_size = waitForDeferred(destination.quotaSize(request))
++            yield new_dest_size
++            new_dest_size = new_dest_size.getResult()
++            diff_size = new_dest_size - old_dest_size
++            if diff_size >= destquota[0]:
++                log.err("Over quota: available %d, need %d" % (destquota[0], diff_size))
++                raise HTTPError(StatusResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, "Over quota"))
++            d = waitForDeferred(destination.quotaSizeAdjust(request, diff_size))
++            yield d
++            d.getResult()
++
++        if deletesource:
++            # Delete the source resource
++            if sourcequota is not None:
++                delete_size = 0 - old_source_size
++                d = waitForDeferred(source.quotaSizeAdjust(request, delete_size))
++                yield d
++                d.getResult()
++
++            delete(source_uri, source.fp, depth)
++            rollback.source_deleted = True
++
++        # Can now commit changes and forget the rollback details
++        rollback.Commit()
++
++        yield response
++        return
++        
++    except:
++        # Roll back changes to original server state. Note this may do nothing
++        # if the rollback has already ocurred or changes already committed.
++        rollback.Rollback()
++        raise
++
++storeResource = deferredGenerator(storeResource)

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/report_principal_property_search.py
 ===================================================================
---- twisted/web2/dav/method/report_principal_property_search.py	(revision 18545)
+--- twisted/web2/dav/method/report_principal_property_search.py	(revision 19725)
 +++ twisted/web2/dav/method/report_principal_property_search.py	(working copy)
 @@ -127,13 +127,8 @@
          matchcount = 0

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.resource.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.resource.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/resource.py
 ===================================================================
---- twisted/web2/dav/resource.py	(revision 18545)
+--- twisted/web2/dav/resource.py	(revision 19737)
 +++ twisted/web2/dav/resource.py	(working copy)
 @@ -40,10 +40,18 @@
      "unauthenticatedPrincipal",
@@ -171,57 +171,7 @@
      ##
      # DAV
      ##
-@@ -509,6 +583,9 @@
-             reactor.callLater(0, getChild)
- 
-         def checkPrivileges(child):
-+            if child is None:
-+                return None
-+
-             if privileges is None:
-                 return child
-    
-@@ -517,14 +594,17 @@
-             return d
- 
-         def gotChild(child, childpath):
--            if child.isCollection():
--                callback(child, childpath + "/")
--                if depth == "infinity":
--                    d = child.findChildren(depth, request, callback, privileges)
--                    d.addCallback(lambda x: reactor.callLater(0, getChild))
--                    return d
-+            if child is None:
-+                callback(None, childpath + "/")
-             else:
--                callback(child, childpath)
-+                if child.isCollection():
-+                    callback(child, childpath + "/")
-+                    if depth == "infinity":
-+                        d = child.findChildren(depth, request, callback, privileges)
-+                        d.addCallback(lambda x: reactor.callLater(0, getChild))
-+                        return d
-+                else:
-+                    callback(child, childpath)
- 
-             reactor.callLater(0, getChild)
- 
-@@ -535,10 +615,10 @@
-                 completionDeferred.callback(None)
-             else:
-                 childpath = joinURL(basepath, childname)
--                child = request.locateResource(childpath)
--                child.addCallback(checkPrivileges)
--                child.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
--                child.addErrback(completionDeferred.errback)
-+                d = request.locateChildResource(self, childname)
-+                d.addCallback(checkPrivileges)
-+                d.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
-+                d.addErrback(completionDeferred.errback)
- 
-         getChild()
- 
-@@ -564,19 +644,21 @@
+@@ -570,19 +644,21 @@
          See L{IDAVResource.authorize}.
          """
          def onError(failure):
@@ -248,7 +198,7 @@
                      response = UnauthorizedResponse(request.credentialFactories,
                                                      request.remoteAddr)
                  else:
-@@ -587,7 +669,7 @@
+@@ -593,7 +669,7 @@
                  # class is supposed to be a FORBIDDEN status code and
                  # "Authorization will not help" according to RFC2616
                  #
@@ -257,7 +207,7 @@
  
              d = self.checkPrivileges(request, privileges, recurse)
              d.addErrback(onErrors)
-@@ -600,16 +682,21 @@
+@@ -606,16 +682,21 @@
  
      def authenticate(self, request):
          def loginSuccess(result):
@@ -283,7 +233,7 @@
  
          authHeader = request.headers.getHeader('authorization')
  
-@@ -625,9 +712,10 @@
+@@ -631,9 +712,10 @@
  
                  # Try to match principals in each principal collection on the resource
                  def gotDetails(details):
@@ -297,7 +247,7 @@
  
                  def login(pcreds):
                      d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -635,13 +723,15 @@
+@@ -641,13 +723,15 @@
  
                      return d
  
@@ -317,7 +267,7 @@
  
      ##
      # ACL
-@@ -650,49 +740,23 @@
+@@ -656,49 +740,23 @@
      def currentPrincipal(self, request):
          """
          @param request: the request being processed.
@@ -376,7 +326,7 @@
          """
          @return: the L{davxml.ACL} element containing the default access control
              list for this resource.
-@@ -704,6 +768,17 @@
+@@ -710,6 +768,17 @@
          #
          return readonlyACL
  
@@ -394,7 +344,7 @@
      def setAccessControlList(self, acl):
          """
          See L{IDAVResource.setAccessControlList}.
-@@ -1032,9 +1107,9 @@
+@@ -1038,9 +1107,9 @@
  
              if myURL == "/":
                  # If we get to the root without any ACLs, then use the default.
@@ -406,7 +356,7 @@
  
          # Dynamically update privileges for those ace's that are inherited.
          if inheritance:
-@@ -1070,7 +1145,7 @@
+@@ -1076,7 +1145,7 @@
                                  # Adjust ACE for inherit on this resource
                                  children = list(ace.children)
                                  children.remove(TwistedACLInheritable())
@@ -415,7 +365,7 @@
                                  aces.append(davxml.ACE(*children))
              else:
                  aces.extend(inherited_aces)
-@@ -1122,7 +1197,7 @@
+@@ -1128,7 +1197,7 @@
                  # Adjust ACE for inherit on this resource
                  children = list(ace.children)
                  children.remove(TwistedACLInheritable())
@@ -424,7 +374,7 @@
                  aces.append(davxml.ACE(*children))
                  
          # Filter out those that do not have a principal match with the current principal
-@@ -1146,49 +1221,69 @@
+@@ -1152,49 +1221,69 @@
  
          This implementation returns an empty set.
          """
@@ -522,7 +472,7 @@
      def samePrincipal(self, principal1, principal2):
          """
          Check whether the two prinicpals are exactly the same in terms of
-@@ -1213,7 +1308,6 @@
+@@ -1219,7 +1308,6 @@
              return False
                  
      def matchPrincipal(self, principal1, principal2, request):
@@ -530,7 +480,7 @@
          """
          Check whether the principal1 is a principal in the set defined by
          principal2.
-@@ -1238,6 +1332,9 @@
+@@ -1244,6 +1332,9 @@
              if isinstance(principal1, davxml.Unauthenticated):
                  yield False
                  return
@@ -540,7 +490,7 @@
              else:
                  yield True
                  return
-@@ -1265,7 +1362,6 @@
+@@ -1271,7 +1362,6 @@
  
          assert principal2 is not None, "principal2 is None"
  
@@ -548,7 +498,7 @@
          # Compare two HRefs and do group membership test as well
          if principal1 == principal2:
              yield True
-@@ -1296,9 +1392,9 @@
+@@ -1302,9 +1392,9 @@
          def testGroup(group):
              # Get principal resource for principal2
              if group and isinstance(group, DAVPrincipalResource):
@@ -561,7 +511,7 @@
                  
              return False
  
-@@ -1426,7 +1522,7 @@
+@@ -1432,7 +1522,7 @@
                  log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
                  yield None
                  return
@@ -570,7 +520,7 @@
  
          if isinstance(principal, davxml.HRef):
              yield principal
-@@ -1511,6 +1607,265 @@
+@@ -1517,6 +1607,265 @@
          return None
  
      ##
@@ -836,20 +786,7 @@
      # HTTP
      ##
  
-@@ -1558,10 +1913,10 @@
-     """
-     DAV resource with no children.
-     """
--    def findChildren(self, depth, request, callback, privileges=None):
-+    def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
-         return succeed(None)
- 
--class DAVPrincipalResource (DAVLeafResource):
-+class DAVPrincipalResource (DAVResource):
-     """
-     Resource representing a WebDAV principal.  (RFC 3744, section 2)
-     """
-@@ -1571,7 +1926,7 @@
+@@ -1577,7 +1926,7 @@
      # WebDAV
      ##
  
@@ -858,7 +795,7 @@
          (dav_namespace, "alternate-URI-set"),
          (dav_namespace, "principal-URL"    ),
          (dav_namespace, "group-member-set" ),
-@@ -1579,14 +1934,11 @@
+@@ -1585,14 +1934,11 @@
      )
  
      def davComplianceClasses(self):
@@ -874,7 +811,7 @@
      def readProperty(self, property, request):
          def defer():
              if type(property) is tuple:
-@@ -1604,10 +1956,10 @@
+@@ -1610,10 +1956,10 @@
                      return davxml.PrincipalURL(davxml.HRef(self.principalURL()))
  
                  if name == "group-member-set":
@@ -887,7 +824,7 @@
  
                  if name == "resourcetype":
                      if self.isCollection():
-@@ -1671,8 +2023,27 @@
+@@ -1677,8 +2023,27 @@
          if self.principalURL() == uri:
              return True
          else:
@@ -916,7 +853,7 @@
  class AccessDeniedError(Exception):
      def __init__(self, errors):
          """ 
-@@ -1712,6 +2083,37 @@
+@@ -1718,6 +2083,37 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.static.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.static.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/static.py
 ===================================================================
---- twisted/web2/dav/static.py	(revision 18545)
+--- twisted/web2/dav/static.py	(revision 19737)
 +++ twisted/web2/dav/static.py	(working copy)
 @@ -28,16 +28,16 @@
  
@@ -131,7 +131,7 @@
      # Workarounds for issues with File
      ##
  
-@@ -132,63 +186,11 @@
+@@ -132,7 +186,10 @@
          return (self.createSimilarFile(self.fp.child(path).path), segments[1:])
  
      def createSimilarFile(self, path):
@@ -141,61 +141,5 @@
 +            principalCollections=self.principalCollections()
 +        )
  
--    def render(self, request):
--        """
--        This is a direct copy of web2.static.render with the
--        listChildren behavior replaced with findChildren to ensure
--        that the current authenticated principal can only list
--        directory contents that they have read permissions for.
--        """
--        if not self.fp.exists():
--            yield responsecode.NOT_FOUND
--            return
--
--        if self.fp.isdir():
--            if request.uri[-1] != "/":
--                # Redirect to include trailing '/' in URI
--                yield RedirectResponse(
--                    request.unparseURL(path=request.path+'/'))
--                return
--            else:
--                ifp = self.fp.childSearchPreauth(*self.indexNames)
--                if ifp:
--                    # Render from the index file
--                    standin = self.createSimilarFile(ifp.path)
--                else:
--                    filtered_aces = waitForDeferred(self.inheritedACEsforChildren(request))
--                    yield filtered_aces
--                    filtered_aces = filtered_aces.getResult()
--
--                    children = []
--
--                    def found(request, uri):
--                        children.append(uri.split("/")[-1].rstrip("/"))
--
--                    x = waitForDeferred(
--                        self.findChildren("1", request, found, (davxml.Read(),),
--                                          inherited_aces=filtered_aces)
--                    )
--                    yield x
--                    x = x.getResult()
--
--                    # Render from a DirectoryLister
--                    standin = dirlist.DirectoryLister(
--                        self.fp.path,
--                        children,
--                        self.contentTypes,
--                        self.contentEncodings,
--                        self.defaultType
--                    )
--                yield standin.render(request)
--                return
--
--        # Do regular resource behavior from superclass
--        yield super(DAVFile, self).render(request)
--    
--    render = deferredGenerator(render)
--
  #
  # Attach method handlers to DAVFile
- #

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.stream.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.stream.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.stream.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/stream.py	(revision 0)
 +++ twisted/web2/dav/stream.py	(revision 0)
-@@ -0,0 +1,74 @@
+@@ -0,0 +1,148 @@
 +##
 +# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
 +#
@@ -77,3 +77,77 @@
 +    def getMD5(self):
 +        assert hasattr(self, "md5value"), "Stream has to be closed first"
 +        return self.md5value
++##
++# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++# 
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++# 
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++# DRI: Cyrus Daboo, cdaboo at apple.com
++##
++
++"""
++Class that implements a stream that calculates the MD5 hash of the data
++as the data is read.
++"""
++
++__all__ = ["MD5StreamWrapper"]
++
++from twisted.internet.defer import Deferred
++from twisted.web2.stream import SimpleStream
++
++import md5
++
++class MD5StreamWrapper(SimpleStream):
++ 
++    def __init__(self, wrap):
++        
++        assert wrap is not None, "Must have a stream to wrap."
++
++        self.stream = wrap
++
++        # Init MD5
++        self.md5 = md5.new()
++    
++    def read(self):
++        assert self.md5 is not None, "Cannot call read after close."
++
++        # Read from wrapped stream first
++        b = self.stream.read()
++        
++        if isinstance(b, Deferred):
++            def _gotData(data):
++                self.md5.update(data)
++                return data
++            b.addCallback(_gotData)
++        else:
++            # Update current MD5 state
++            self.md5.update(str(b))
++    
++        return b
++    
++    def close(self):
++        # Close out the MD5 hash
++        self.md5value = self.md5.hexdigest()
++        self.md5 = None
++
++        SimpleStream.close(self)
++    
++    def getMD5(self):
++        assert hasattr(self, "md5value"), "Stream has to be closed first"
++        return self.md5value

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/test/test_acl.py
 ===================================================================
---- twisted/web2/dav/test/test_acl.py	(revision 18545)
+--- twisted/web2/dav/test/test_acl.py	(revision 19725)
 +++ twisted/web2/dav/test/test_acl.py	(working copy)
 @@ -30,6 +30,7 @@
  from twisted.web2.auth import basic

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/test/test_prop.py
 ===================================================================
---- twisted/web2/dav/test/test_prop.py	(revision 18545)
+--- twisted/web2/dav/test/test_prop.py	(revision 19725)
 +++ twisted/web2/dav/test/test_prop.py	(working copy)
 @@ -21,6 +21,8 @@
  #

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/test/test_resource.py
 ===================================================================
---- twisted/web2/dav/test/test_resource.py	(revision 18545)
+--- twisted/web2/dav/test/test_resource.py	(revision 19725)
 +++ twisted/web2/dav/test/test_resource.py	(working copy)
 @@ -192,13 +192,10 @@
  class AccessTests(TestCase):

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/xattrprops.py
 ===================================================================
---- twisted/web2/dav/xattrprops.py	(revision 18545)
+--- twisted/web2/dav/xattrprops.py	(revision 19725)
 +++ twisted/web2/dav/xattrprops.py	(working copy)
 @@ -66,16 +66,8 @@
          deadPropertyXattrPrefix = "user."

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.iweb.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.iweb.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.iweb.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/iweb.py
 ===================================================================
---- twisted/web2/iweb.py	(revision 18545)
+--- twisted/web2/iweb.py	(revision 19725)
 +++ twisted/web2/iweb.py	(working copy)
 @@ -41,14 +41,23 @@
          """

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.log.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.log.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.log.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/log.py
 ===================================================================
---- twisted/web2/log.py	(revision 18545)
+--- twisted/web2/log.py	(revision 19725)
 +++ twisted/web2/log.py	(working copy)
 @@ -88,7 +88,7 @@
  class LogWrapperResource(resource.WrapperResource):

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.server.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.server.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.server.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,6 +1,6 @@
 Index: twisted/web2/server.py
 ===================================================================
---- twisted/web2/server.py	(revision 18545)
+--- twisted/web2/server.py	(revision 19737)
 +++ twisted/web2/server.py	(working copy)
 @@ -26,6 +26,7 @@
  from twisted.web2 import http_headers
@@ -64,50 +64,23 @@
          d.addCallback(lambda res, req: res.renderHTTP(req), self)
          d.addCallback(self._cbFinishRender)
          d.addErrback(self._processingFailed)
-@@ -320,8 +340,6 @@
-                     url = "/" + "/".join(path)
-                 else:
-                     url = "/"
--        
--                self._rememberURLForResource(quote(url), res)
+@@ -321,7 +341,6 @@
+         if newpath is StopTraversal:
+             # We need to rethink how to do this.
+             #if newres is res:
+-                self._rememberResource(res, url)
                  return res
              #else:
              #    raise ValueError("locateChild must not return StopTraversal with a resource other than self.")
-@@ -342,17 +360,16 @@
+@@ -337,7 +356,6 @@
                  self.prepath.append(self.postpath.pop(0))
  
          child = self._getChild(None, newres, newpath, updatepaths=updatepaths)
--        self._rememberURLForResource(quote(url), child)
+-        self._rememberResource(child, url)
  
          return child
  
--    _resourcesByURL = weakref.WeakKeyDictionary()
--
--    def _rememberURLForResource(self, url, resource):
-+    def _rememberResource(self, resource, url):
-         """
--        Remember the URL of visited resources.
-+        Remember the URL of a visited resources.
-         """
-         self._resourcesByURL[resource] = url
-+        self._urlsByResource[url] = resource
-+        return resource
- 
-     def urlForResource(self, resource):
-         """
-@@ -367,10 +384,7 @@
- 
-         @return: the URL of C{resource} if known, otherwise C{None}.
-         """
--        try:
--            return self._resourcesByURL[resource]
--        except KeyError:
--            return None
-+        return self._resourcesByURL.get(resource, None)
- 
-     def locateResource(self, url):
-         """
-@@ -385,7 +399,8 @@
+@@ -386,7 +404,8 @@
              The contained response will have a status code of
              L{responsecode.BAD_REQUEST}.
          """
@@ -117,80 +90,3 @@
  
          #
          # Parse the URL
-@@ -406,19 +421,71 @@
-                 "URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
-             ))
- 
--        segments = path.split("/")
-+        # Looked for cached value
-+        cached = self._urlsByResource.get(path, None)
-+        if cached is not None:
-+            return defer.succeed(cached)
-+
-+        segments = unquote(path).split("/")
-         assert segments[0] == "", "URL path didn't begin with '/': %s" % (path,)
-         segments = segments[1:]
--        segments = map(unquote, segments)
- 
-         def notFound(f):
-             f.trap(http.HTTPError)
--            if f.response.code != responsecode.NOT_FOUND:
--                raise f
-+            if f.value.response.code != responsecode.NOT_FOUND:
-+                return f
-             return None
- 
--        return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
-+        d = defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
-+        d.addCallback(self._rememberResource, path)
-+        d.addErrback(notFound)
-+        return d
- 
-+    def locateChildResource(self, parent, child_name):
-+        """
-+        Looks up the child resource with the given name given the parent
-+        resource.  This is similar to locateResource(), but doesn't have to
-+        start the lookup from the root resource, so it is potentially faster.
-+        @param parent: the parent of the resource being looked up.
-+        @param child_name: the name of the child of C{parent} to looked up.
-+            to C{parent}.
-+        @return: a L{Deferred} resulting in the L{IResource} at the
-+            given URL or C{None} if no such resource can be located.
-+        @raise HTTPError: If C{url} is not a URL on the site that this
-+            request is being applied to.  The contained response will
-+            have a status code of L{responsecode.BAD_GATEWAY}.
-+        @raise HTTPError: If C{url} contains a query or fragment.
-+            The contained response will have a status code of
-+            L{responsecode.BAD_REQUEST}.
-+        """
-+        if parent is None or child_name is None:
-+            return defer.succeed(None)
-+
-+        parent_resource = self.urlForResource(parent)
-+
-+        assert parent_resource is not None
-+
-+        url = joinURL(parent_resource, child_name)
-+
-+        cached = self._urlsByResource.get(url, None)
-+        if cached is not None:
-+            return defer.succeed(cached)
-+
-+        assert "/" not in child_name, "Child name may not contain '/': %s" % (child_name,)
-+
-+        segment = unquote(child_name)
-+
-+        def notFound(f):
-+            f.trap(http.HTTPError)
-+            if f.value.response.code != responsecode.NOT_FOUND:
-+                return f
-+            return None
-+
-+        d = defer.maybeDeferred(self._getChild, None, parent, [segment], updatepaths=False)
-+        d.addCallback(self._rememberResource, url)
-+        d.addErrback(notFound)
-+        return d
-+
-     def _processingFailed(self, reason):
-         if reason.check(http.HTTPError) is not None:
-             # If the exception was an HTTPError, leave it alone

Modified: CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/lib-patches/Twisted/twisted.web2.test.test_httpauth.patch	2007-03-06 18:03:59 UTC (rev 1326)
@@ -1,106 +1,552 @@
 Index: twisted/web2/test/test_httpauth.py
 ===================================================================
---- twisted/web2/test/test_httpauth.py	(revision 18545)
+--- twisted/web2/test/test_httpauth.py	(revision 19737)
 +++ twisted/web2/test/test_httpauth.py	(working copy)
-@@ -1,3 +1,6 @@
+@@ -1,3 +1,5 @@
 +import md5
-+import time
 +from twisted.internet import address
  from twisted.trial import unittest
- from twisted.internet import defer
  from twisted.cred import error
-@@ -12,8 +15,13 @@
+ from twisted.web2.auth import basic, digest, wrapper
+@@ -9,12 +11,29 @@
+ import base64
+ 
+ class FakeDigestCredentialFactory(digest.DigestCredentialFactory):
++    """
++    A Fake Digest Credential Factory that generates a predictable
++    nonce and opaque
++    """
++
++    def __init__(self, *args, **kwargs):
++        super(FakeDigestCredentialFactory, self).__init__(*args, **kwargs)
++
++        self.privateKey = "0"
++
      def generateNonce(self):
++        """
++        Generate a static nonce
++        """
          return '178288758716122392881254770685'
  
 -    def generateOpaque(self):
 -        return '1041524039'
-+    def generateOpaque(self, nonce, clientip):
-+        # 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(0)))
-+        digest = md5.new(key + "0").hexdigest()
-+        ekey = key.encode('base64')
-+        return "%s-%s" % (digest, ekey.replace('\n', ''),)
++    def _getTime(self):
++        """
++        Return a stable time
++        """
++        return 0
  
++
  class BasicAuthTestCase(unittest.TestCase):
      def setUp(self):
-@@ -56,32 +64,63 @@
+         self.credentialFactory = basic.BasicCredentialFactory('foo')
+@@ -54,39 +73,315 @@
+                           self.credentialFactory.decode,
+                           response, _trivial_GET)
  
- challengeResponse = ('digest', {'nonce': '178288758716122392881254770685', 
-                                 'qop': 'auth', 'realm': 'test realm', 
+-challengeResponse = ('digest', {'nonce': '178288758716122392881254770685',
+-                                'qop': 'auth', 'realm': 'test realm',
 -                                'algorithm': 'md5', 'opaque': '1041524039'})
-+                                'algorithm': 'md5',
-+                                'opaque': '75c4bd95b96b7b7341c646c6502f0833-MTc4Mjg4NzU4NzE2MTIyMzkyODgxMjU0NzcwNjg1LHJlbW90ZWhvc3QsMA=='})
  
 -authRequest = 'username="username", realm="test realm", nonce="178288758716122392881254770685", uri="/write/", response="62f388be1cf678fbdfce87910871bcc5", opaque="1041524039", algorithm="md5", cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000001, qop="auth"'
-+cnonce = "29fc54aa1641c6fa0e151419361c8f23"
++clientAddress = address.IPv4Address('TCP', '127.0.0.1', 80)
  
-+authRequest1 = 'username="username", realm="test realm", nonce="%s", uri="/write/", response="%s", opaque="%s", algorithm="md5", cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000001, qop="auth"'
-+authRequest2 = 'username="username", realm="test realm", nonce="%s", uri="/write/", response="%s", opaque="%s", algorithm="md5", cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000002, qop="auth"'
++challengeOpaque = ('75c4bd95b96b7b7341c646c6502f0833-MTc4Mjg4NzU'
++                   '4NzE2MTIyMzkyODgxMjU0NzcwNjg1LHJlbW90ZWhvc3Q'
++                   'sMA==')
 +
++challengeNonce = '178288758716122392881254770685'
++
++challengeResponse = ('digest',
++                     {'nonce': challengeNonce,
++                      'qop': 'auth', 'realm': 'test realm',
++                      'algorithm': 'md5',
++                      'opaque': challengeOpaque})
++
++cnonce = "29fc54aa1641c6fa0e151419361c8f23"
++
++authRequest1 = ('username="username", realm="test realm", nonce="%s", '
++                'uri="/write/", response="%s", opaque="%s", algorithm="md5", '
++                'cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000001, '
++                'qop="auth"')
++
++authRequest2 = ('username="username", realm="test realm", nonce="%s", '
++                'uri="/write/", response="%s", opaque="%s", algorithm="md5", '
++                'cnonce="29fc54aa1641c6fa0e151419361c8f23", nc=00000002, '
++                'qop="auth"')
++
  namelessAuthRequest = 'realm="test realm",nonce="doesn\'t matter"'
  
++
  class DigestAuthTestCase(unittest.TestCase):
++    """
++    Test the behavior of DigestCredentialFactory
++    """
++
      def setUp(self):
--        self.credentialFactory = FakeDigestCredentialFactory('md5', 
-+        self.credentialFactory = digest.DigestCredentialFactory('md5', 
-                                                              'test realm')
+-        self.credentialFactory = FakeDigestCredentialFactory('md5',
+-                                                             'test realm')
++        """
++        Create a DigestCredentialFactory for testing
++        """
++        self.credentialFactory = digest.DigestCredentialFactory('md5',
++                                                                'test realm')
  
+-    def testGetChallenge(self):
+-        self.assertEquals(
+-            self.credentialFactory.getChallenge(None),
+-            challengeResponse[1])
 +    def getDigestResponse(self, challenge, ncount):
++        """
++        Calculate the response for the given challenge
++        """
 +        nonce = challenge.get('nonce')
 +        algo = challenge.get('algorithm').lower()
 +        qop = challenge.get('qop')
-+        
+ 
+-    def testResponse(self):
+-        challenge = self.credentialFactory.getChallenge(None)
 +        expected = digest.calcResponse(
-+            digest.calcHA1(algo, "username", "test realm", "password", nonce, cnonce),
++            digest.calcHA1(algo,
++                           "username",
++                           "test realm",
++                           "password",
++                           nonce,
++                           cnonce),
 +            algo, nonce, ncount, cnonce, qop, "GET", "/write/", None
-+        )
++            )
 +        return expected
+ 
+-        creds = self.credentialFactory.decode(authRequest, _trivial_GET)
++    def test_getChallenge(self):
++        """
++        Test that all the required fields exist in the challenge,
++        and that the information matches what we put into our
++        DigestCredentialFactory
++        """
 +
-     def testGetChallenge(self):
--        self.assertEquals(
--            self.credentialFactory.getChallenge(None),
--            challengeResponse[1])
-+        challenge = self.credentialFactory.getChallenge(address.IPv4Address('TCP', "127.0.0.1", 80))
++        challenge = 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"))
++
++    def test_response(self):
++        """
++        Test that we can decode a valid response to our challenge
++        """
++
++        challenge = self.credentialFactory.getChallenge(clientAddress)
++
++        clientResponse = authRequest1 % (
++            challenge['nonce'],
++            self.getDigestResponse(challenge, "00000001"),
++            challenge['opaque'])
++
++        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
+         self.failUnless(creds.checkPassword('password'))
  
-     def testResponse(self):
+-    def testFailsWithDifferentMethod(self):
 -        challenge = self.credentialFactory.getChallenge(None)
-+        challenge = self.credentialFactory.getChallenge(address.IPv4Address('TCP', "127.0.0.1", 80))
++    def test_multiResponse(self):
++        """
++        Test that multiple responses to to a single challenge are handled
++        successfully.
++        """
  
--        creds = self.credentialFactory.decode(authRequest, _trivial_GET)
-+        clientResponse = authRequest1 % (challenge['nonce'], self.getDigestResponse(challenge, "00000001"), challenge['opaque'])
-+        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
-         self.failUnless(creds.checkPassword('password'))
- 
-+    def testMultiResponse(self):
-+        challenge = self.credentialFactory.getChallenge(address.IPv4Address('TCP', "127.0.0.1", 80))
+-        creds = self.credentialFactory.decode(authRequest, SimpleRequest(None, 'POST', '/'))
++        challenge = self.credentialFactory.getChallenge(clientAddress)
 +
-+        clientResponse = authRequest1 % (challenge['nonce'], self.getDigestResponse(challenge, "00000001"), challenge['opaque'])
++        clientResponse = authRequest1 % (
++            challenge['nonce'],
++            self.getDigestResponse(challenge, "00000001"),
++            challenge['opaque'])
++
 +        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
 +        self.failUnless(creds.checkPassword('password'))
 +
-+        clientResponse = authRequest2 % (challenge['nonce'], self.getDigestResponse(challenge, "00000002"), challenge['opaque'])
++        clientResponse = authRequest2 % (
++            challenge['nonce'],
++            self.getDigestResponse(challenge, "00000002"),
++            challenge['opaque'])
++
 +        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
 +        self.failUnless(creds.checkPassword('password'))
 +
-     def testFailsWithDifferentMethod(self):
--        challenge = self.credentialFactory.getChallenge(None)
-+        challenge = self.credentialFactory.getChallenge(address.IPv4Address('TCP', "127.0.0.1", 80))
-         
--        creds = self.credentialFactory.decode(authRequest, SimpleRequest(None, 'POST', '/'))
-+        clientResponse = authRequest1 % (challenge['nonce'], self.getDigestResponse(challenge, "00000001"), challenge['opaque'])
-+        creds = self.credentialFactory.decode(clientResponse, SimpleRequest(None, 'POST', '/'))
++    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)
++
++        clientResponse = authRequest1 % (
++            challenge['nonce'],
++            self.getDigestResponse(challenge, "00000001"),
++            challenge['opaque'])
++
++        creds = self.credentialFactory.decode(clientResponse,
++                                              SimpleRequest(None, 'POST', '/'))
          self.failIf(creds.checkPassword('password'))
  
-     def testNoUsername(self):
-@@ -221,7 +260,7 @@
+-    def testNoUsername(self):
+-        self.assertRaises(error.LoginFailed, self.credentialFactory.decode, namelessAuthRequest, _trivial_GET)
++    def test_noUsername(self):
++        """
++        Test that login fails when our response does not contain a username
++        """
  
++        self.assertRaises(error.LoginFailed,
++                          self.credentialFactory.decode,
++                          namelessAuthRequest,
++                          _trivial_GET)
++
++    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)
++
++        clientResponse = authRequest1 % (
++            challenge['nonce'],
++            self.getDigestResponse(challenge, "00000001"),
++            challenge['opaque'])
++
++        creds = self.credentialFactory.decode(clientResponse, _trivial_GET)
++
++        self.failUnless(creds.checkHash(
++                md5.md5('username:test realm:password').hexdigest()))
++
++        self.failIf(creds.checkHash(
++                md5.md5('username:test realm:bogus').hexdigest()))
++
++    def test_invalidOpaque(self):
++        """
++        Test that login fails when the opaque does not contain all the required
++        parts.
++        """
++
++        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
++
++        challenge = credentialFactory.getChallenge(clientAddress)
++
++        self.assertRaises(
++            error.LoginFailed,
++            credentialFactory.verifyOpaque,
++            'badOpaque',
++            challenge['nonce'],
++            clientAddress.host)
++
++        badOpaque = ('foo-%s' % (
++                'nonce,clientip'.encode('base64').strip('\n'),))
++
++        self.assertRaises(
++            error.LoginFailed,
++            credentialFactory.verifyOpaque,
++            badOpaque,
++            challenge['nonce'],
++            clientAddress.host)
++
++    def test_incompatibleNonce(self):
++        """
++        Test that login fails when the given nonce from the response, does not
++        match the nonce encoded in the opaque.
++        """
++
++        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
++
++        challenge = credentialFactory.getChallenge(clientAddress)
++
++        badNonceOpaque = credentialFactory.generateOpaque(
++            '1234567890',
++            clientAddress.host)
++
++        self.assertRaises(
++            error.LoginFailed,
++            credentialFactory.verifyOpaque,
++            badNonceOpaque,
++            challenge['nonce'],
++            clientAddress.host)
++
++    def test_incompatibleClientIp(self):
++        """
++        Test that the login fails when the request comes from a client ip
++        other than what is encoded in the opaque.
++        """
++
++        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
++
++        challenge = credentialFactory.getChallenge(clientAddress)
++
++        badNonceOpaque = credentialFactory.generateOpaque(
++            challenge['nonce'],
++            '10.0.0.1')
++
++        self.assertRaises(
++            error.LoginFailed,
++            credentialFactory.verifyOpaque,
++            badNonceOpaque,
++            challenge['nonce'],
++            clientAddress.host)
++
++    def test_oldNonce(self):
++        """
++        Test that the login fails when the given opaque is older than
++        DigestCredentialFactory.CHALLENGE_LIFETIME_SECS
++        """
++
++        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
++
++        challenge = credentialFactory.getChallenge(clientAddress)
++
++        key = '%s,%s,%s' % (challenge['nonce'],
++                            clientAddress.host,
++                            '-137876876')
++        digest = md5.new(key + credentialFactory.privateKey).hexdigest()
++        ekey = key.encode('base64')
++
++        oldNonceOpaque = '%s-%s' % (digest, ekey.strip('\n'))
++
++        self.assertRaises(
++            error.LoginFailed,
++            credentialFactory.verifyOpaque,
++            oldNonceOpaque,
++            challenge['nonce'],
++            clientAddress.host)
++
++    def test_mismatchedOpaqueChecksum(self):
++        """
++        Test that login fails when the opaque checksum fails verification
++        """
++
++        credentialFactory = FakeDigestCredentialFactory('md5', 'test realm')
++
++        challenge = credentialFactory.getChallenge(clientAddress)
++
++
++        key = '%s,%s,%s' % (challenge['nonce'],
++                            clientAddress.host,
++                            '0')
++
++        digest = md5.new(key + 'this is not the right pkey').hexdigest()
++
++        badChecksum = '%s-%s' % (digest,
++                                 key.encode('base64').strip('\n'))
++
++        self.assertRaises(
++            error.LoginFailed,
++            credentialFactory.verifyOpaque,
++            badChecksum,
++            challenge['nonce'],
++            clientAddress.host)
++
++    def test_incompatibleCalcHA1Options(self):
++        """
++        Test that the appropriate error is raised when any of the
++        pszUsername, pszRealm, or pszPassword arguments are specified with
++        the preHA1 keyword argument.
++        """
++
++        arguments = (
++            ("user", "realm", "password", "preHA1"),
++            (None, "realm", None, "preHA1"),
++            (None, None, "password", "preHA1"),
++            )
++
++        for pszUsername, pszRealm, pszPassword, preHA1 in arguments:
++            self.assertRaises(
++                TypeError,
++                digest.calcHA1,
++                "md5",
++                pszUsername,
++                pszRealm,
++                pszPassword,
++                "nonce",
++                "cnonce",
++                preHA1=preHA1
++                )
++
++
+ from zope.interface import implements
+ from twisted.cred import portal, checkers
+ 
+@@ -105,6 +400,7 @@
+         """
+         self.username = username
+ 
++
+ class TestAuthRealm(object):
+     """
+     Test realm that supports the IHTTPUser interface
+@@ -169,7 +465,7 @@
+         del self.credFactory
+         del self.protectedResource
+ 
+-    def test_AuthenticatedRequest(self):
++    def test_authenticatedRequest(self):
+         """
+         Test that after successful authentication the request provides
+         IAuthenticatedRequest and that the request.avatar implements
+@@ -207,7 +503,7 @@
+         d.addCallback(checkRequest)
+         return d
+ 
+-    def test_AllowedMethods(self):
++    def test_allowedMethods(self):
+         """
+         Test that unknown methods result in a 401 instead of a 405 when
+         authentication hasn't been completed.
+@@ -219,17 +515,18 @@
+                                         [self.credFactory],
+                                         self.portal,
+                                         interfaces=(IHTTPUser,))
+-        d = self.assertResponse((root, 'http://localhost/'),
+-                                (401,
+-                                 {'WWW-Authenticate': [('basic',
+-                                                        {'realm': "test realm"})]},
+-                                None))
++        d = self.assertResponse(
++            (root, 'http://localhost/'),
++            (401,
++             {'WWW-Authenticate': [('basic',
++                                    {'realm': "test realm"})]},
++             None))
+ 
+         self.method = 'GET'
+ 
+         return d
+ 
+-    def test_UnauthorizedResponse(self):
++    def test_unauthorizedResponse(self):
+         """
+         Test that a request with no credentials results in a
+         valid Unauthorized response.
+@@ -240,22 +537,24 @@
+                                         interfaces=(IHTTPUser,))
+ 
+         def makeDeepRequest(res):
+-            return self.assertResponse((root,
+-                                        'http://localhost/foo/bar/baz/bax'),
+-                                       (401,
+-                                        {'WWW-Authenticate': [('basic',
+-                                                               {'realm': "test realm"})]},
+-                                        None))
++            return self.assertResponse(
++                (root,
++                 'http://localhost/foo/bar/baz/bax'),
++                (401,
++                 {'WWW-Authenticate': [('basic',
++                                        {'realm': "test realm"})]},
++                 None))
+ 
+-        d = self.assertResponse((root, 'http://localhost/'),
+-                                (401,
+-                                 {'WWW-Authenticate': [('basic',
+-                                                        {'realm': "test realm"})]},
+-                                 None))
++        d = self.assertResponse(
++            (root, 'http://localhost/'),
++            (401,
++             {'WWW-Authenticate': [('basic',
++                                    {'realm': "test realm"})]},
++             None))
+ 
+         return d.addCallback(makeDeepRequest)
+ 
+-    def test_BadCredentials(self):
++    def test_badCredentials(self):
+         """
+         Test that a request with bad credentials results in a valid
+         Unauthorized response
+@@ -267,16 +566,17 @@
+ 
+         credentials = base64.encodestring('bad:credentials')
+ 
+-        d = self.assertResponse((root, 'http://localhost/',
+-                                 {'authorization': ('basic', credentials)}),
+-                                (401,
+-                                 {'WWW-Authenticate': [('basic',
+-                                                        {'realm': "test realm"})]},
+-                                 None))
++        d = self.assertResponse(
++            (root, 'http://localhost/',
++             {'authorization': [('basic', credentials)]}),
++            (401,
++             {'WWW-Authenticate': [('basic',
++                                    {'realm': "test realm"})]},
++             None))
+ 
+         return d
+ 
+-    def test_SuccessfulLogin(self):
++    def test_successfulLogin(self):
+         """
+         Test that a request with good credentials results in the
+         appropriate response from the protected resource
+@@ -297,7 +597,7 @@
+ 
+         return d
+ 
+-    def test_WrongScheme(self):
++    def test_wrongScheme(self):
+         """
+         Test that a request with credentials for a scheme that is not
+         advertised by this resource results in the appropriate
+@@ -319,16 +619,17 @@
+ 
+         return d
+ 
+-    def test_MultipleWWWAuthenticateSchemes(self):
++    def test_multipleWWWAuthenticateSchemes(self):
+         """
+         Test that our unauthorized response can contain challenges for
+         multiple authentication schemes.
+         """
+-        root = wrapper.HTTPAuthResource(self.protectedResource,
+-                                        (basic.BasicCredentialFactory('test realm'),
+-                                         FakeDigestCredentialFactory('md5', 'test realm')),
+-                                        self.portal,
+-                                        interfaces=(IHTTPUser,))
++        root = wrapper.HTTPAuthResource(
++            self.protectedResource,
++            (basic.BasicCredentialFactory('test realm'),
++             FakeDigestCredentialFactory('md5', 'test realm')),
++            self.portal,
++            interfaces=(IHTTPUser,))
+ 
+         d = self.assertResponse((root, 'http://localhost/', {}),
+                                 (401,
+@@ -339,15 +640,18 @@
+ 
+         return d
+ 
+-    def test_AuthorizationAgainstMultipleSchemes(self):
++    def test_authorizationAgainstMultipleSchemes(self):
+         """
+-        Test that we can authenticate to either authentication scheme.
++        Test that we can successfully authenticate when presented
++        with multiple WWW-Authenticate headers
+         """
+-        root = wrapper.HTTPAuthResource(self.protectedResource,
+-                                        (basic.BasicCredentialFactory('test realm'),
+-                                         FakeDigestCredentialFactory('md5', 'test realm')),
++
++        root = wrapper.HTTPAuthResource(
++            self.protectedResource,
++            (basic.BasicCredentialFactory('test realm'),
++             FakeDigestCredentialFactory('md5', 'test realm')),
+                                         self.portal,
+-                                        interfaces=(IHTTPUser,))
++            interfaces=(IHTTPUser,))
+ 
+         def respondBasic(ign):
+             credentials = base64.encodestring('username:password')
+@@ -362,7 +666,7 @@
+ 
          def respond(ign):
              d = self.assertResponse((root, 'http://localhost/',
 -                                     {'authorization': authRequest}),
@@ -108,3 +554,21 @@
                                      (200,
                                       {},
                                       None))
+@@ -377,7 +681,7 @@
+ 
+         return d
+ 
+-    def test_WrappedResourceGetsFullSegments(self):
++    def test_wrappedResourceGetsFullSegments(self):
+         """
+         Test that the wrapped resource gets all the URL segments in it's
+         locateChild.
+@@ -405,7 +709,7 @@
+ 
+         return d
+ 
+-    def test_InvalidCredentials(self):
++    def test_invalidCredentials(self):
+         """
+         Malformed or otherwise invalid credentials (as determined by
+         the credential factory) should result in an Unauthorized response

Modified: CalendarServer/branches/users/dreid/new-twisted-2/run
===================================================================
--- CalendarServer/branches/users/dreid/new-twisted-2/run	2007-03-06 17:59:25 UTC (rev 1325)
+++ CalendarServer/branches/users/dreid/new-twisted-2/run	2007-03-06 18:03:59 UTC (rev 1326)
@@ -476,8 +476,8 @@
     proto="svn";
     ;;
 esac;
-svn_uri="${proto}://svn.twistedmatrix.com/svn/Twisted/branches/dav-acl-1608-2";
-svn_get "Twisted" "${twisted}" "${svn_uri}" 18545;
+svn_uri="${proto}://svn.twistedmatrix.com/svn/Twisted/branches/dav-acl-1608-3";
+svn_get "Twisted" "${twisted}" "${svn_uri}" 19737;
 
 # No py_build step, since we tend to do edit Twisted, we want the sources in
 # PYTHONPATH, not a build directory.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070306/5f38eeeb/attachment.html


More information about the calendarserver-changes mailing list