[CalendarServer-changes] [346] CalendarServer/branches/users/cdaboo/dropbox

source_changes at macosforge.org source_changes at macosforge.org
Wed Nov 1 10:45:20 PST 2006


Revision: 346
          http://trac.macosforge.org/projects/calendarserver/changeset/346
Author:   cdaboo at apple.com
Date:     2006-11-01 10:45:19 -0800 (Wed, 01 Nov 2006)

Log Message:
-----------
Merge from trunk.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/dropbox/conf/repository-static.xml
    CalendarServer/branches/users/cdaboo/dropbox/conf/repository.dtd
    CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.static.patch
    CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.server.patch
    CalendarServer/branches/users/cdaboo/dropbox/support/submit
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/authkerb.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/caldavxml.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/customxml.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/directory.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/logging.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/__init__.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/static.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/dropbox/conf/repository-proxy.xml
    CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.dav.auth.patch
    CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch

Removed Paths:
-------------
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/schedule.py

Copied: CalendarServer/branches/users/cdaboo/dropbox/conf/repository-proxy.xml (from rev 341, CalendarServer/trunk/conf/repository-proxy.xml)
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/conf/repository-proxy.xml	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/dropbox/conf/repository-proxy.xml	2006-11-01 18:45:19 UTC (rev 346)
@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006 Apple Computer, 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.
+ -->
+
+<!DOCTYPE repository SYSTEM "repository.dtd">
+
+<repository>
+
+  <docroot auto-principal-collection-set="no">
+    <collection>
+      <pytype>twisted.web2.dav.static.DAVFile</pytype>
+      <properties>
+        <acl>
+          <ace>
+            <principal><authenticated/></principal>
+            <grant><privilege><read/></privilege></grant>
+            <protected/>
+          </ace>
+          <ace>
+            <principal><href>/principals/users/admin</href></principal>
+            <grant><privilege><all/></privilege></grant>
+            <protected/>
+            <inheritable/>
+          </ace>
+        </acl>
+        <!--
+          Must explicitly set which principal hierarchies will be
+          listed in WebDAV properties.  The order of these will
+          determine how a user id will map to a principal in a
+          particular hierarchy if an id appears in more than one.
+         -->
+        <prop><principal-collection-set xmlns="DAV:"><href>/principals/localusers/</href><href>/principals/users/</href><href>/principals/users/</href><href>/principals/groups/</href><href>/principals/resources/</href></principal-collection-set></prop>
+      </properties>
+      <members>
+        <!--
+          We must define the calendar home location before the
+          principals as auto-provisioning of accounts occurs when the
+          principal collections are created and we need to have the
+          calendar home path setup by then.
+         -->
+        <collection name="calendars" tag="calendars">
+          <pytype>twistedcaldav.static.CalDAVFile</pytype>
+          <properties>
+            <acl>
+              <ace>
+                <principal><authenticated/></principal>
+                <grant><privilege><read/></privilege></grant>
+                <protected/>
+              </ace>
+            </acl>
+          </properties>
+          <members>
+            <collection name="users">
+              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
+              <properties>
+                <acl>
+                  <ace>
+                    <principal><authenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                  </ace>
+                </acl>
+              </properties>
+              <members/>
+            </collection>
+            <collection name="groups">
+              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
+              <properties>
+                <acl>
+                  <ace>
+                    <principal><authenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                  </ace>
+                </acl>
+              </properties>
+              <members/>
+            </collection>
+            <collection name="resources">
+              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
+              <properties>
+                <acl>
+                  <ace>
+                    <principal><authenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                  </ace>
+                </acl>
+              </properties>
+              <members/>
+            </collection>
+            <collection name="public">
+              <properties>
+                <acl>
+                  <ace>
+                    <principal><unauthenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                    <inheritable/>
+                  </ace>
+                  <ace>
+                    <principal><authenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                    <inheritable/>
+                  </ace>
+                </acl>
+              </properties>
+              <members/>
+            </collection>
+          </members>
+        </collection>
+        <collection name="principals">
+          <pytype>twistedcaldav.directory.DirectoryPrincipalProvisioningResource</pytype>
+          <params>
+            <param>
+              <key>DirectoryNode</key>
+              <value>/Search</value>
+            </param>
+          </params>
+          <properties>
+            <acl>
+              <ace>
+                <principal><authenticated/></principal>
+                <grant><privilege><read/></privilege></grant>
+                <protected/>
+              </ace>
+            </acl>
+          </properties>
+          <members>
+            <collection name="users">
+              <pytype>twistedcaldav.directory.DirectoryUserPrincipalProvisioningResource</pytype>
+              <properties>
+                <acl>
+                  <ace>
+                    <principal><authenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                  </ace>
+                </acl>
+              </properties>
+              <members/>
+            </collection>
+            <collection name="groups">
+              <pytype>twistedcaldav.directory.DirectoryGroupPrincipalProvisioningResource</pytype>
+              <properties>
+                <acl>
+                  <ace>
+                    <principal><authenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                  </ace>
+                </acl>
+              </properties>
+              <members/>
+            </collection>
+            <collection name="resources">
+              <pytype>twistedcaldav.directory.DirectoryResourcePrincipalProvisioningResource</pytype>
+              <properties>
+                <acl>
+                  <ace>
+                    <principal><authenticated/></principal>
+                    <grant><privilege><read/></privilege></grant>
+                    <protected/>
+                  </ace>
+                </acl>
+              </properties>
+              <members/>
+            </collection>
+            <collection name="localusers" tag="principals">
+              <pytype>twistedcaldav.static.CalendarPrincipalCollectionFile</pytype>
+                <properties>
+                  <acl>
+                    <ace>
+                      <principal><href>/principals/users/admin</href></principal>
+                      <grant><privilege><read/></privilege></grant>
+                      <protected/>
+                    </ace>
+                  </acl>
+                </properties>
+              <members/>
+            </collection>
+          </members>
+        </collection>
+      </members>
+    </collection>
+  </docroot>
+
+  <authentication>
+    <basic enable="yes" onlyssl="yes" credentials="directory">
+      <realm></realm>
+    </basic>
+    <digest enable="no" onlyssl="no" credentials="property">
+      <realm></realm>
+    </digest>
+    <kerberos enable="no" onlyssl="no">
+      <service></service>
+    </kerberos>
+  </authentication>
+
+<accounts>
+  <user>
+    <uid>proxy</uid>
+    <pswd>proxy</pswd>
+    <name>User who can authorize as someone else</name>
+    <canproxy/>
+  </user>
+</accounts>
+
+</repository>

Modified: CalendarServer/branches/users/cdaboo/dropbox/conf/repository-static.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/conf/repository-static.xml	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/conf/repository-static.xml	2006-11-01 18:45:19 UTC (rev 346)
@@ -52,7 +52,7 @@
           </properties>
           <members>
             <collection name="users" tag="principals">
-              <pytype>twistedcaldav.static.CalendarUserPrincipalProvisioningResource</pytype>
+              <pytype>twistedcaldav.static.CalendarPrincipalCollectionFile</pytype>
               <properties>
                 <acl>
                   <ace>
@@ -134,6 +134,12 @@
       <pswd>admin</pswd>
       <name>Super User</name>
     </user>
+    <user>
+      <uid>proxy</uid>
+      <pswd>proxy</pswd>
+      <name>User who can authorize as someone else</name>
+      <canproxy/>
+    </user>
     <user repeat='99'>
       <uid>user%02d</uid>
       <pswd>user%02d</pswd>

Modified: CalendarServer/branches/users/cdaboo/dropbox/conf/repository.dtd
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/conf/repository.dtd	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/conf/repository.dtd	2006-11-01 18:45:19 UTC (rev 346)
@@ -66,7 +66,7 @@
   	
   <!ELEMENT accounts (user*) >
 
-    <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, acl?, quota?, autorespond?)>
+    <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, acl?, quota?, autorespond?, canproxy?)>
       <!ATTLIST user repeat CDATA "1">
       <!ELEMENT uid         (#PCDATA)>
       <!ELEMENT pswd        (#PCDATA)>
@@ -76,3 +76,5 @@
       <!ELEMENT calendar    (#PCDATA)>
       <!ELEMENT quota       (#PCDATA)>
       <!ELEMENT autorespond EMPTY>
+      <!ELEMENT canproxy    EMPTY>
+      
\ No newline at end of file

Copied: CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.dav.auth.patch (from rev 341, CalendarServer/trunk/lib-patches/Twisted/twisted.dav.auth.patch)
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.dav.auth.patch	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.dav.auth.patch	2006-11-01 18:45:19 UTC (rev 346)
@@ -0,0 +1,66 @@
+Index: twisted/web2/dav/auth.py
+===================================================================
+--- twisted/web2/dav/auth.py	(revision 18545)
++++ twisted/web2/dav/auth.py	(working copy)
+@@ -40,7 +40,7 @@
+ 
+     def requestAvatar(self, avatarId, mind, *interfaces):
+         if IPrincipal in interfaces:
+-            return IPrincipal, davxml.Principal(davxml.HRef(avatarId))
++            return IPrincipal, davxml.Principal(davxml.HRef(avatarId[0])), davxml.Principal(davxml.HRef(avatarId[1]))
+         
+         raise NotImplementedError("Only IPrincipal interface is supported")
+ 
+@@ -52,9 +52,23 @@
+ class PrincipalCredentials(object):
+     implements(IPrincipalCredentials)
+ 
+-    def __init__(self, principal, principalURI, credentials):
+-        self.principal = principal
+-        self.principalURI = principalURI
++    def __init__(self, authnPrincipal, authnURI, authzPrincipal, authzURI, credentials):
++        """
++        Initialize with both authentication and authorization values. Note that in most cases theses will be the same
++        since HTTP auth makes no distinction between the two - but we may be layering some addition auth on top of this
++        (.e.g.. proxy auth, cookies, forms etc) that make result in authentication and authorization being different.
++
++        @param authnPrincipal: L{IDAVPrincipalResource} for the authenticated principal.
++        @param authnURI: C{str} containing the URI of the authenticated principal.
++        @param authzPrincipal: L{IDAVPrincipalResource} for the authorized principal.
++        @param authzURI: C{str} containing the URI of the authorized principal.
++        @param credentials: L{IPrincipalCredentials} for the authentication credentials.
++        """
++        
++        self.authnPrincipal = authnPrincipal
++        self.authnURI = authnURI
++        self.authzPrincipal = authzPrincipal
++        self.authzURI = authzURI
+         self.credentials = credentials
+ 
+     def checkPassword(self, password):
+@@ -66,19 +80,20 @@
+ 
+     credentialInterfaces = (IPrincipalCredentials,)
+ 
+-    def _cbPasswordMatch(self, matched, principalURI):
++    def _cbPasswordMatch(self, matched, principalURIs):
+         if matched:
+-            return principalURI
++            # We return both URIs
++            return principalURIs
+         else:
+             raise error.UnauthorizedLogin(
+-                "Bad credentials for: %s" % (principalURI,))
++                "Bad credentials for: %s" % (principalURIs[0],))
+ 
+     def requestAvatarId(self, credentials):
+         pcreds = IPrincipalCredentials(credentials)
+-        pswd = str(pcreds.principal.readDeadProperty(TwistedPasswordProperty))
++        pswd = str(pcreds.authnPrincipal.readDeadProperty(TwistedPasswordProperty))
+ 
+         d = defer.maybeDeferred(credentials.checkPassword, pswd)
+-        d.addCallback(self._cbPasswordMatch, pcreds.principalURI)
++        d.addCallback(self._cbPasswordMatch, (pcreds.authnURI, pcreds.authzURI,))
+         return d
+ 
+ ##

Modified: CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-11-01 18:45:19 UTC (rev 346)
@@ -98,7 +98,212 @@
                  child.addCallback(checkPrivileges)
                  child.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
                  child.addErrback(completionDeferred.errback)
-@@ -1511,6 +1561,265 @@
+@@ -574,9 +624,9 @@
+             def onErrors(failure):
+                 failure.trap(AccessDeniedError)
+                 
+-                # If we were unauthorized to start with (no Authorization header from client) then
++                # If we were unauthenticated to start with (no Authorization header from client) then
+                 # we should return an unauthorized response instead to force the client to login if it can
+-                if request.user == davxml.Principal(davxml.Unauthenticated()):
++                if request.authnUser == davxml.Principal(davxml.Unauthenticated()):
+                     response = UnauthorizedResponse(request.credentialFactories,
+                                                     request.remoteAddr)
+                 else:
+@@ -600,8 +650,13 @@
+ 
+     def authenticate(self, request):
+         def loginSuccess(result):
+-            request.user = result[1]
+-            return request.user
++            """
++            @param result: returned tuple from auth.DAVRealm.requestAvatar.
++            """
++            
++            request.authnUser = result[1]
++            request.authzUser = result[2]
++            return (request.authnUser, request.authzUser,)
+ 
+         if not (
+             hasattr(request, 'portal') and 
+@@ -608,8 +663,9 @@
+             hasattr(request, 'credentialFactories') and
+             hasattr(request, 'loginInterfaces')
+         ):
+-            request.user = davxml.Principal(davxml.Unauthenticated())
+-            return request.user
++            request.authnUser = davxml.Principal(davxml.Unauthenticated())
++            request.authzUser = davxml.Principal(davxml.Unauthenticated())
++            return (request.authnUser, request.authzUser,)
+ 
+         authHeader = request.headers.getHeader('authorization')
+ 
+@@ -625,9 +681,11 @@
+ 
+                 # Try to match principals in each principal collection on the resource
+                 def gotDetails(details):
+-                    principal = IDAVPrincipalResource(details[0])
+-                    principalURI = details[1]
+-                    return PrincipalCredentials(principal, principalURI, creds)
++                    authnPrincipal = IDAVPrincipalResource(details[0][0])
++                    authnURI = details[0][1]
++                    authzPrincipal = IDAVPrincipalResource(details[1][0])
++                    authzURI = details[1][1]
++                    return PrincipalCredentials(authnPrincipal, authnURI, authzPrincipal, authzURI, creds)
+ 
+                 def login(pcreds):
+                     d = request.portal.login(pcreds, None, *request.loginInterfaces)
+@@ -635,7 +693,7 @@
+ 
+                     return d
+ 
+-                d = self.findPrincipalForAuthID(request, creds.username)
++                d = self.principalsForAuthID(request, creds.username)
+                 d.addCallback(gotDetails).addCallback(login)
+ 
+                 return d
+@@ -640,8 +698,9 @@
+ 
+                 return d
+         else:
+-            request.user = davxml.Principal(davxml.Unauthenticated())
+-            return request.user
++            request.authnUser = davxml.Principal(davxml.Unauthenticated())
++            request.authzUser = davxml.Principal(davxml.Unauthenticated())
++            return (request.authnUser, request.authzUser,)
+ 
+     ##
+     # ACL
+@@ -650,10 +709,10 @@
+     def currentPrincipal(self, request):
+         """
+         @param request: the request being processed.
+-        @return: the current principal, as derived from the given request.
++        @return: the current authorized principal, as derived from the given request.
+         """
+-        if hasattr(request, "user"):
+-            return request.user
++        if hasattr(request, "authzUser"):
++            return request.authzUser
+         else:
+             return unauthenticatedPrincipal
+ 
+@@ -1149,8 +1208,12 @@
+ 
+         return []
+ 
+-    def findPrincipalForAuthID(self, request, authid):
++    def principalsForAuthID(self, request, authid):
+         """
++        Return authentication and authorization prinicipal identifiers for the
++        authentication identifer passed in. In this implementation authn and authz
++        principals are the same.
++
+         @param request: the L{IRequest} for the request in progress.
+         @param authid: a string containing the
+             authentication/authorization identifier for the principal
+@@ -1155,12 +1218,55 @@
+         @param authid: a string containing the
+             authentication/authorization identifier for the principal
+             to lookup.
+-        @return: a deferred tuple of C{(principal, principalURI)}
+-            where: C{principal} is the L{Principal} that is found;
+-            C{principalURI} is the C{str} URI of the principal. 
++        @return: a deferred tuple of two tuples. Each tuple is
++            C{(principal, principalURI)} where: C{principal} is the L{Principal}
++            that is found; {principalURI} is the C{str} URI of the principal.
++            The first tuple corresponds to authentication identifiers,
++            the second to authorization identifiers.
+             It will errback with an HTTPError(responsecode.FORBIDDEN) if
+             the principal isn't found.
+         """
++
++        # Try to match principals in each principal collection on the resource
++        d = waitForDeferred(self.findPrincipalForAuthID(request, authid))
++        yield d
++        result = d.getResult()
++        
++        if result is not None:
++            authnPrincipal = result[0]
++            authnURI = result[1]
++            d = waitForDeferred(self.authorizationPrincipal(request, authid, authnPrincipal, authnURI))
++            yield d
++            authzPrincipal, authzURI = d.getResult()
++            yield ((authnPrincipal, authnURI), (authzPrincipal, authzURI),)
++            return
++        else:
++            principalCollections = waitForDeferred(self.principalCollections(request))
++            yield principalCollections
++            principalCollections = principalCollections.getResult()
++
++            if len(principalCollections) == 0:
++                log.msg("DAV:principal-collection-set property cannot be found on the resource being authorized: %s" % self)
++            else:
++                log.msg("Could not find principal matching user id: %s" % authid)
++            raise HTTPError(responsecode.FORBIDDEN)
++
++    principalsForAuthID = deferredGenerator(principalsForAuthID)
++
++    def findPrincipalForAuthID(self, request, authid):
++        """
++        Return authentication and authoirization prinicipal identifiers for the
++        authentication identifer passed in. In this implementation authn and authz
++        principals are the same.
++
++        @param request: the L{IRequest} for the request in progress.
++        @param authid: a string containing the
++            authentication/authorization identifier for the principal
++            to lookup.
++        @return: a tuple of C{(principal, principalURI)} where: C{principal} is the L{Principal}
++            that is found; {principalURI} is the C{str} URI of the principal.
++            If not found return None.
++        """
+         # Try to match principals in each principal collection on the resource
+         collections = waitForDeferred(self.principalCollections(request))
+         yield collections
+@@ -1173,22 +1279,30 @@
+             yield principal
+             principal = principal.getResult()
+ 
+-            if isPrincipalResource(principal):
+-                yield (principal, principalURI)
++            if isPrincipalResource(principal) and principal.exists():
++                yield principal, principalURI
+                 return
+         else:
+-            principalCollections = waitForDeferred(self.principalCollections(request))
+-            yield principalCollections
+-            principalCollections = principalCollections.getResult()
+-
+-            if len(principalCollections) == 0:
+-                log.msg("DAV:principal-collection-set property cannot be found on the resource being authorized: %s" % self)
+-            else:
+-                log.msg("Could not find principal matching user id: %s" % authid)
+-            raise HTTPError(responsecode.FORBIDDEN)
++            yield None
++            return
+ 
+     findPrincipalForAuthID = deferredGenerator(findPrincipalForAuthID)
+ 
++    def authorizationPrincipal(self, request, authid, authnPrincipal, authnURI):
++        """
++        Determine the authorization principal for the given request and authentication principal.
++        This implementation simply uses aht authentication principalk as the authoization principal.
++        
++        @param request: the L{IRequest} for the request in progress.
++        @param authid: a string containing the uthentication/authorization identifier
++            for the principal to lookup.
++        @param authnPrincipal: the L{IDAVPrincipal} for the authenticated principal
++        @param authnURI: a C{str} containing the URI of the authenticated principal
++        @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
++            resource and URI respectively.
++        """
++        return succeed((authnPrincipal, authnURI,))
++        
+     def samePrincipal(self, principal1, principal2):
+         """
+         Check whether the two prinicpals are exactly the same in terms of
+@@ -1511,6 +1625,265 @@
          return None
  
      ##
@@ -364,7 +569,7 @@
      # HTTP
      ##
  
-@@ -1558,7 +1867,7 @@
+@@ -1558,7 +1931,7 @@
      """
      DAV resource with no children.
      """
@@ -373,7 +578,7 @@
          return succeed(None)
  
  class DAVPrincipalResource (DAVLeafResource):
-@@ -1712,6 +2021,37 @@
+@@ -1712,6 +2085,37 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  

Modified: CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-11-01 18:45:19 UTC (rev 346)
@@ -96,3 +96,12 @@
      # Workarounds for issues with File
      ##
  
+@@ -164,7 +213,7 @@
+                     children = []
+ 
+                     def found(request, uri):
+-                        children.append(uri.split("/")[-1].rstrip("/"))
++                        children.append(uri.rstrip("/").split("/")[-1])
+ 
+                     x = waitForDeferred(
+                         self.findChildren("1", request, found, (davxml.Read(),),

Copied: CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch (from rev 341, CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch)
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	2006-11-01 18:45:19 UTC (rev 346)
@@ -0,0 +1,25 @@
+Index: twisted/web2/dav/test/test_resource.py
+===================================================================
+--- twisted/web2/dav/test/test_resource.py	(revision 18545)
++++ twisted/web2/dav/test/test_resource.py	(working copy)
+@@ -282,7 +282,8 @@
+         # Has auth; should allow
+ 
+         request = SimpleRequest(site, "GET", "/")
+-        request.user = davxml.Principal(davxml.HRef("/users/d00d"))
++        request.authnUser = davxml.Principal(davxml.HRef("/users/d00d"))
++        request.authzUser = davxml.Principal(davxml.HRef("/users/d00d"))
+         d = request.locateResource('/')
+         d.addCallback(_checkPrivileges)
+         d.addCallback(expectOK)
+@@ -380,8 +381,8 @@
+         return succeed(davPrivilegeSet)
+ 
+     def currentPrincipal(self, request):
+-        if hasattr(request, "user"):
+-            return request.user
++        if hasattr(request, "authzUser"):
++            return request.authzUser
+         else:
+             return davxml.Principal(davxml.Unauthenticated())
+ 

Modified: CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.server.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.server.patch	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.server.patch	2006-11-01 18:45:19 UTC (rev 346)
@@ -11,96 +11,139 @@
  
  
  """This is a web-sever which integrates with the twisted.internet
-@@ -150,6 +152,9 @@
+@@ -150,17 +152,32 @@
              self._initialprepath = kw['prepathuri']
              del kw['prepathuri']
  
 +        self._resourcesByURL = {}
-+        self._resourcesFromURL = {}
++        self._urlsByResource = {}
 +
          # Copy response filters from the class
          self.responseFilters = self.responseFilters[:]
          self.files = {}
-@@ -156,7 +161,9 @@
          self.resources = []
          http.Request.__init__(self, *args, **kw)
  
 -    def addResponseFilter(self, f, atEnd=False):
-+    def addResponseFilter(self, f, atEnd=False, onlyOnce = False):
-+        if onlyOnce and f in self.responseFilters:
++    def addResponseFilter(self, filter, atEnd=False, onlyOnce=False):
++        """
++        Add a response filter to this request.
++        Response filters are applied to the response to this request in order.
++        @param filter: a callable which takes an response argument and returns
++            a response object.
++        @param atEnd: if C{True}, C{filter} is added at the end of the list of
++            response filters; if C{False}, it is added to the beginning.
++        @param onlyOnce: if C{True}, C{filter} is not added to the list of
++            response filters if it already in the list.
++        """
++        if onlyOnce and filter in self.responseFilters:
 +            return
          if atEnd:
-             self.responseFilters.append(f)
+-            self.responseFilters.append(f)
++            self.responseFilters.append(filter)
          else:
-@@ -263,8 +270,15 @@
-             failedDeferred = self._processingFailed(failure.Failure())
-             return
+-            self.responseFilters.insert(0, f)
++            self.responseFilters.insert(0, filter)
+ 
+     def unparseURL(self, scheme=None, host=None, port=None,
+                    path=None, params=None, querystring=None, fragment=None):
+@@ -265,6 +282,7 @@
          
-+        def _registerResource(child):
-+            url = "/" + "/".join(self.prepath)
-+            self._resourcesByURL[child] = url
-+            self._resourcesFromURL[url] = child
-+            return child
-+        
          d = defer.Deferred()
          d.addCallback(self._getChild, self.site.resource, self.postpath)
-+        d.addCallback(_registerResource)
++        d.addCallback(self._rememberResource, "/" + "/".join(self.prepath))
          d.addCallback(lambda res, req: res.renderHTTP(req), self)
          d.addCallback(self._cbFinishRender)
          d.addErrback(self._processingFailed)
-@@ -321,7 +335,7 @@
+@@ -320,8 +338,6 @@
+                     url = "/" + "/".join(path)
                  else:
                      url = "/"
-         
+-        
 -                self._rememberURLForResource(quote(url), res)
-+                #self._rememberURLForResource(quote(url), res)
                  return res
              #else:
              #    raise ValueError("locateChild must not return StopTraversal with a resource other than self.")
-@@ -342,12 +356,10 @@
+@@ -342,17 +358,16 @@
                  self.prepath.append(self.postpath.pop(0))
  
          child = self._getChild(None, newres, newpath, updatepaths=updatepaths)
 -        self._rememberURLForResource(quote(url), child)
-+        #self._rememberURLForResource(quote(url), child)
  
          return child
  
 -    _resourcesByURL = weakref.WeakKeyDictionary()
 -
-     def _rememberURLForResource(self, url, resource):
+-    def _rememberURLForResource(self, url, resource):
++    def _rememberResource(self, resource, url):
          """
-         Remember the URL of visited resources.
-@@ -387,6 +399,11 @@
+-        Remember the URL of visited resources.
++        Remember the URL of a visited resources.
          """
-         if url is None: return None
+         self._resourcesByURL[resource] = url
++        self._urlsByResource[url] = resource
++        return resource
  
-+        try:
-+            return succeed(self._resourcesFromURL[url])
-+        except KeyError:
-+            pass
+     def urlForResource(self, resource):
+         """
+@@ -367,10 +382,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,8 +397,13 @@
+             The contained response will have a status code of
+             L{responsecode.BAD_REQUEST}.
+         """
+-        if url is None: return None
++        if url is None:
++            return None
+ 
++        cached = self._urlsByResource.get(url, None)
++        if cached is not None:
++            return succeed(cached)
 +
          #
          # Parse the URL
          #
-@@ -417,7 +434,52 @@
-                 raise f
+@@ -406,19 +423,62 @@
+                 "URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
+             ))
+ 
+-        segments = path.split("/")
++        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
++                return f
              return None
  
 -        return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
-+        def _registerResource(child):
-+            self._resourcesByURL[child] = path
-+            self._resourcesFromURL[path] = child
-+            return child
-+        
 +        d = defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
-+        d.addCallback(_registerResource)
++        d.addCallback(self._rememberResource, path)
++        d.addErrback(notFound)
 +        return d
-+
-+    def locateChildResource(self, res, childpath):
+ 
++    def locateChildResource(self, parent, child_name):
 +        """
-+        Looks up the child resource with the given name given the parent resource.
-+        @param uri: The URL last path segment of the desired resource.
++        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
@@ -110,30 +153,30 @@
 +            The contained response will have a status code of
 +            L{responsecode.BAD_REQUEST}.
 +        """
-+        if res is None or childpath is None: return None
-+        
-+        url = joinURL(self.urlForResource(res), childpath)
-+        try:
-+            return succeed(self._resourcesFromURL[url])
-+        except KeyError:
-+            pass
++        if parent is None or child_name is None:
++            return None
 +
-+        segment = unquote(childpath)
++        url = joinURL(self.urlForResource(parent), child_name)
 +
++        cached = self._urlsByResource.get(url, None)
++        if cached is not None:
++            return 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.response.code != responsecode.NOT_FOUND:
-+                raise f
++                return f
 +            return None
 +
-+        def _registerResource(child):
-+            self._resourcesByURL[child] = url
-+            self._resourcesFromURL[url] = child
-+            return child
-+        
-+        d = defer.maybeDeferred(self._getChild, None, res, [segment], updatepaths=False)
-+        d.addCallback(_registerResource)
++        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/cdaboo/dropbox/support/submit
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/support/submit	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/support/submit	2006-11-01 18:45:19 UTC (rev 346)
@@ -36,11 +36,11 @@
 
 usage ()
 {
-  program="$(basename "$0") release";
+  program="$(basename "$0")";
 
   if [ "${1-}" != "-" ]; then echo "$1"; echo; fi;
 
-  echo "Usage: ${program} release version";
+  echo "Usage: ${program} release";
 
   if [ "${1-}" == "-" ]; then return 0; fi;
   exit 64;

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/authkerb.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/authkerb.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/authkerb.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -113,11 +113,11 @@
                 kerberos.checkPassword(creds.username, creds.password, creds.service, creds.default_realm)
             except kerberos.BasicAuthError, ex:
                 logging.err("%s" % (ex[0],), system="BasicKerberosCredentialsChecker")
-                raise error.UnauthorizedLogin("Bad credentials for: %s (%s)" % (pcreds.principalURI, ex[0],))
+                raise error.UnauthorizedLogin("Bad credentials for: %s (%s)" % (pcreds.authnURI, ex[0],))
             else:
-                return succeed(pcreds.principalURI)
+                return succeed((pcreds.authnURI, pcreds.authzURI,))
         
-        raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.principalURI,))
+        raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
 
 class NegotiateCredentials:
     """
@@ -207,14 +207,14 @@
 
     def requestAvatarId(self, credentials):
         # NB If we get here authentication has already succeeded as it is done in NegotiateCredentialsFactory.decode
-        # So all we need to do is return the principal URI from the credentials.
+        # So all we need to do is return the principal URIs from the credentials.
 
-        # If there is no calendar principal URI then the calendar user is disabled.
+        # Look for proper credential type.
         pcreds = IPrincipalCredentials(credentials)
 
         creds = pcreds.credentials
         if isinstance(creds, NegotiateCredentials):
-            return succeed(pcreds.principalURI)
+            return succeed((pcreds.authnURI, pcreds.authzURI,))
         
-        raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.principalURI,))
+        raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
 

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/caldavxml.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/caldavxml.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -1134,13 +1134,28 @@
         test = str(self)
         if self.caseless:
             test = test.lower()
-        for value in values:
+
+        def _textCompare(s):
             if self.caseless:
-                if value.lower().find(test) != -1:
-                    return not self.negate
+                if s.lower().find(test) != -1:
+                    return True, not self.negate
             else:
-                if value.find(test) != -1:
-                    return not self.negate
+                if s.find(test) != -1:
+                    return True, not self.negate
+            return False, False
+
+        for value in values:
+            # NB Its possible that we have a text list value which appears as a Pythin list,
+            # so we need to check for that an iterate over thr list.
+            if isinstance(value, list):
+                for subvalue in value:
+                    matched, result = _textCompare(subvalue)
+                    if matched:
+                        return result
+            else:
+                matched, result = _textCompare(value)
+                if matched:
+                    return result
         
         return self.negate
 

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/customxml.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/customxml.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -103,7 +103,7 @@
     namespace = twisted_dav_namespace
     name = "notifications"
 
-class DropBoxHomeURL (davxml.WebDAVTextElement):
+class DropBoxHomeURL (davxml.WebDAVElement):
     """
     A principal property to indicate the location of the drop box home.
     (Apple Extension to CalDAV)
@@ -115,7 +115,7 @@
 
     allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
 
-class NotificationsURL (davxml.WebDAVTextElement):
+class NotificationsURL (davxml.WebDAVElement):
     """
     A principal property to indicate the location of the notification collection.
     (Apple Extension to CalDAV)
@@ -127,6 +127,163 @@
 
     allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
 
+class Notification(davxml.WebDAVElement):
+    """
+    Root element for XML data in a notification resource.
+    """
+    namespace = twisted_dav_namespace
+    name = "notification"
+
+    allowed_children = {
+        (twisted_dav_namespace, "action"     ): (1, 1),
+        (twisted_dav_namespace, "time-stamp" ): (1, 1),
+        (twisted_dav_namespace, "auth-id"    ): (1, 1),
+        (twisted_dav_namespace, "old-etag"   ): (1, 1),
+        (twisted_dav_namespace, "new-etag"   ): (1, 1),
+        (twisted_dav_namespace, "old-uri"    ): (1, 1),
+        (twisted_dav_namespace, "new_uri"    ): (1, 1),
+    }
+
+class Action (davxml.WebDAVElement):
+    """
+    A property to indicate the action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "action"
+    hidden = True
+    protected = True
+
+    allowed_children = {
+        (twisted_dav_namespace, "created"    ): (0, 1),
+        (twisted_dav_namespace, "modified"   ): (0, 1),
+        (twisted_dav_namespace, "deleted"    ): (0, 1),
+        (twisted_dav_namespace, "copiedto"   ): (0, 1),
+        (twisted_dav_namespace, "copiedfrom" ): (0, 1),
+        (twisted_dav_namespace, "movedout"   ): (0, 1),
+        (twisted_dav_namespace, "movedin"    ): (0, 1),
+    }
+
+class Created (davxml.WebDAVEmptyElement):
+    """
+    A property value to indicate the created action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "created"
+
+class Modified (davxml.WebDAVEmptyElement):
+    """
+    A property value to indicate the modified action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "modified"
+
+class Deleted (davxml.WebDAVEmptyElement):
+    """
+    A property value to indicate the deleted action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "deleted"
+
+class CopiedTo (davxml.WebDAVEmptyElement):
+    """
+    A property value to indicate the copied to action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "copiedto"
+
+class CopiedFrom (davxml.WebDAVEmptyElement):
+    """
+    A property value to indicate the copied from action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "copiedfrom"
+
+class MovedTo (davxml.WebDAVEmptyElement):
+    """
+    A property value to indicate the moved to action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "movedto"
+
+class MovedFrom (davxml.WebDAVEmptyElement):
+    """
+    A property value to indicate the moved from action of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "movedfrom"
+
+class TimeStamp (davxml.WebDAVTextElement):
+    """
+    A property to indicate the timestamp of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "time-stamp"
+    hidden = True
+    protected = True
+
+class AuthID (davxml.WebDAVTextElement):
+    """
+    A property to indicate the authorization identitifer of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "auth-id"
+    hidden = True
+    protected = True
+
+class OldETag (davxml.WebDAVTextElement):
+    """
+    A property to indicate the old ETag of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "old-etag"
+    hidden = True
+    protected = True
+
+class NewETag (davxml.WebDAVTextElement):
+    """
+    A property to indicate the new ETag of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "new-etag"
+    hidden = True
+    protected = True
+
+class OldURI (davxml.WebDAVElement):
+    """
+    A property to indicate the old URI of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "old-uri"
+    hidden = True
+    protected = True
+
+    allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
+class NewURI (davxml.WebDAVElement):
+    """
+    A property to indicate the new URI of a notification resource.
+    (Apple Extension to CalDAV)
+    """
+    namespace = twisted_dav_namespace
+    name = "new-uri"
+    hidden = True
+    protected = True
+
+    allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
 ##
 # Extensions to davxml.ResourceType
 ##

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/directory.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/directory.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -39,6 +39,7 @@
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
 from twisted.web2.dav.auth import IPrincipalCredentials
+from twisted.web2.dav.auth import TwistedPropertyChecker
 from twisted.web2.dav.static import DAVFile
 from twisted.web2.dav.util import joinURL
 from twisted.web2.http import HTTPError
@@ -57,26 +58,24 @@
 import os
 import unicodedata
 
-class DirectoryCredentialsChecker:
-    implements(checkers.ICredentialsChecker)
+class DirectoryCredentialsChecker (TwistedPropertyChecker):
 
-    credentialInterfaces = (IPrincipalCredentials,)
-
     def requestAvatarId(self, credentials):
 
         # If there is no calendar principal URI then the calendar user is disabled.
         pcreds = IPrincipalCredentials(credentials)
-        if not pcreds.principal.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
-            raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.principalURI,))
+        if not pcreds.authnPrincipal.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
+            # Try regular password check
+            return TwistedPropertyChecker.requestAvatarId(self, credentials)
 
         creds = pcreds.credentials
         if isinstance(creds, UsernamePassword):
             user = creds.username
             pswd = creds.password
-            if opendirectory.authenticateUser(pcreds.principal.directory(), user, pswd):
-                return succeed(pcreds.principalURI)
+            if opendirectory.authenticateUser(pcreds.authnPrincipal.directory(), user, pswd):
+                return succeed((pcreds.authnURI, pcreds.authzURI,))
         
-        raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.principalURI,))
+        raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
 
 class DirectoryPrincipalFile (CalendarPrincipalFile):
     """

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/logging.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/logging.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/logging.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -25,6 +25,8 @@
 import time
 
 from twisted.python import log
+from twisted.web2 import iweb
+from twisted.web2.dav import davxml
 from twisted.web2.log import BaseCommonAccessLoggingObserver
 
 #
@@ -117,6 +119,41 @@
             self.rotate()
         self.f.write(message + '\n')
 
+    def emit(self, eventDict):
+        if eventDict.get('interface') is not iweb.IRequest:
+            return
+
+        request = eventDict['request']
+        response = eventDict['response']
+        loginfo = eventDict['loginfo']
+        firstLine = '%s %s HTTP/%s' %(
+            request.method,
+            request.uri,
+            '.'.join([str(x) for x in request.clientproto]))
+        
+        # Try to determine authentication and authorization identifiers
+        uid = "-"
+        if hasattr(request, "authnUser"):
+            if isinstance(request.authnUser.children[0], davxml.HRef):
+                uid = str(request.authnUser.children[0])
+                if hasattr(request, "authzUser") and str(request.authzUser.children[0]) != uid:
+                    uid += " as %s" % (str(request.authzUser.children[0]),)
+        
+
+        self.logMessage(
+            '%s - %s [%s] "%s" %s %d "%s" "%s"' %(
+                request.remoteAddr.host,
+                uid,
+                self.logDateString(
+                    response.headers.getHeader('date', 0)),
+                firstLine,
+                response.code,
+                loginfo.bytesSent,
+                request.headers.getHeader('referer', '-'),
+                request.headers.getHeader('user-agent', '-')
+                )
+            )
+
     def start(self):
         """
         Start logging. Open the log file and log an 'open' message.

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/__init__.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/__init__.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -35,6 +35,5 @@
     "report_calquery",
     "report_multiget",
     "report_freebusy",
-    "schedule",
     "schedule_common",
 ]

Deleted: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/schedule.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/schedule.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -1,58 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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
-##
-
-"""
-CalDAV SCHEDULE method.
-"""
-
-__all__ = ["http_SCHEDULE"]
-
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twisted.web2.dav.util import parentForURL
-
-from twistedcaldav import caldavxml
-from twistedcaldav.method.schedule_common import processScheduleRequest
-
-def http_SCHEDULE(self, request):
-
-    """
-    The CalDAV SCHEDULE method.
-    
-    This uses a generator function yielding either L{waitForDeferred} objects or L{Response} objects.
-    This allows for code that follows a 'linear' execution pattern rather than having to use nested
-    L{Deferred} callbacks. The logic is easier to follow this way plus we don't run into deep nesting
-    issues which the other approach would have with large numbers of recipients.
-    """
-
-    #
-    # Check authentication and access controls
-    #
-    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
-    yield parent
-    parent = parent.getResult()
-
-    d = waitForDeferred(parent.authorize(request, (caldavxml.Schedule(),)))
-    yield d
-    d.getResult()
-        
-    # Initiate deferred generator
-    d = waitForDeferred(processScheduleRequest(self, "SCHEDULE", request))
-    yield d
-    yield d.getResult()
-
-http_SCHEDULE = deferredGenerator(http_SCHEDULE)

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -119,6 +119,7 @@
 ELEMENT_CALENDAR = "calendar"
 ELEMENT_QUOTA = "quota"
 ELEMENT_AUTORESPOND = "autorespond"
+ELEMENT_CANPROXY = "canproxy"
 ATTRIBUTE_REPEAT = "repeat"
 
 def startServer(docroot, repo, doacct, doacl, dossl,
@@ -813,6 +814,8 @@
                 self.acl.parseXML(child)
             elif child._get_localName() == ELEMENT_AUTORESPOND:
                 self.autorespond = True
+            elif child._get_localName() == ELEMENT_CANPROXY:
+                CalDAVResource.proxyUsers.add(self.uid)
 
 class Authentication:
     """

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/resource.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/resource.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -38,6 +38,7 @@
 from twisted.internet import reactor
 from twisted.internet.defer import Deferred, maybeDeferred, succeed
 from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twisted.python import log
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
 from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalResource
@@ -76,6 +77,10 @@
     # resources to that size, or C{None} for no limit.
     sizeLimit = None
 
+    # Set containing user ids of all the users who have been given
+    # the right to authorize as someone else.
+    proxyUsers = set()
+
     ##
     # HTTP
     ##
@@ -226,6 +231,61 @@
 
         return super(CalDAVResource, self).accessControlList(*args, **kwargs)
 
+    def authorizationPrincipal(self, request, authid, authnPrincipal, authnURI):
+        """
+        Determine the authorization principal for the given request and authentication principal.
+        This implementation looks for an X-Authorize-As header value to use as the authoization principal.
+        
+        @param request: the L{IRequest} for the request in progress.
+        @param authid: a string containing the uthentication/authorization identifier
+            for the principal to lookup.
+        @param authnPrincipal: the L{IDAVPrincipal} for the authenticated principal
+        @param authnURI: a C{str} containing the URI of the authenticated principal
+        @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
+            resource and URI respectively.
+        """
+
+        # Look for X-Authorize-As Header
+        authz = request.headers.getRawHeaders("x-authorize-as")
+        if authz is not None and (len(authz) == 1):
+            # Substitute the authz value for principal look up
+            authz = authz[0]
+
+        # See if authenticated uid is a proxy user
+        if authid in CalDAVResource.proxyUsers:
+            if authz:
+                if authz in CalDAVResource.proxyUsers:
+                    log.msg("Cannot proxy as another proxy: user '%s' as user '%s'" % (authid, authz))
+                    raise HTTPError(responsecode.UNAUTHORIZED)
+                else:
+                    d = waitForDeferred(self.findPrincipalForAuthID(request, authz))
+                    yield d
+                    result = d.getResult()
+
+                    if result is not None:
+                        log.msg("Allow proxy: user '%s' as '%s'" % (authid, authz,))
+                        authzPrincipal = result[0]
+                        authzURI = result[1]
+                        yield authzPrincipal, authzURI
+                        return
+                    else:
+                        log.msg("Could not find proxy user id: '%s'" % authid)
+                        raise HTTPError(responsecode.UNAUTHORIZED)
+            else:
+                log.msg("Cannot authenticate proxy user '%s' without X-Authorize-As header" % (authid, ))
+                raise HTTPError(responsecode.UNAUTHORIZED)
+        elif authz:
+            log.msg("Cannot proxy: user '%s' as '%s'" % (authid, authz,))
+            raise HTTPError(responsecode.UNAUTHORIZED)
+        else:
+            # No proxy - do default behavior
+            d = waitForDeferred(super(CalDAVResource, self).authorizationPrincipal(request, authid, authnPrincipal, authnURI))
+            yield d
+            yield d.getResult()
+            return
+
+    authorizationPrincipal = deferredGenerator(authorizationPrincipal)
+
     ##
     # CalDAV
     ##

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/static.py	2006-11-01 00:07:35 UTC (rev 345)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/static.py	2006-11-01 18:45:19 UTC (rev 346)
@@ -27,11 +27,12 @@
     "CalendarHomeFile",
     "CalendarHomeProvisioningFile",
     "CalendarPrincipalFile",
-    "CalendarUserPrincipalProvisioningResource",
+    "CalendarPrincipalCollectionFile",
 ]
 
 import os
 import errno
+from posixpath import basename
 from urlparse import urlsplit
 
 from twisted.internet.defer import deferredGenerator, fail, succeed, waitForDeferred
@@ -869,7 +870,7 @@
                 DropBox.provision(self, (homeURL, home))
 
 
-class CalendarUserPrincipalProvisioningResource (CalendarPrincipalCollectionResource, DAVFile):
+class CalendarPrincipalCollectionFile (CalendarPrincipalCollectionResource, DAVFile):
     """
     L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
     as needed.
@@ -893,34 +894,13 @@
         """
         pass
     
-    def render(self, request):
-        return StatusResponse(
-            responsecode.OK,
-            "This collection contains user principal resources",
-            title=self.displayName()
-        )
-
-    def getChild(self, name):
-        if name == "": return self
-
-        child_fp = self.fp.child(name)
-        if child_fp.exists():
-            assert child_fp.isfile()
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
         else:
-            assert self.exists()
-            assert self.isCollection()
+            # TODO: Fix this - not sure how to get URI for second argument of __init__
+            return CalendarPrincipalFile(path, joinURL(self.principalCollectionURL(), basename(path)))
 
-            # FIXME: Do a real lookup of what's valid here
-            if name[0] == ".": return None
-            if len(name) > 8: return None
-
-            child_fp.open("w").close()
-
-        return CalendarPrincipalFile(child_fp.path, joinURL(self.principalCollectionURL(), name))
-
-    def createSimilarFile(self, path):
-        raise NotImplementedError("Not allowed.")
-
     def principalSearchPropertySet(self):
         """
         See L{IDAVResource.principalSearchPropertySet}.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061101/3ddb804a/attachment.html


More information about the calendarserver-changes mailing list