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

source_changes at macosforge.org source_changes at macosforge.org
Thu Nov 2 12:29:23 PST 2006


Revision: 354
          http://trac.macosforge.org/projects/calendarserver/changeset/354
Author:   cdaboo at apple.com
Date:     2006-11-02 12:29:22 -0800 (Thu, 02 Nov 2006)

Log Message:
-----------
merge -r324:351 http://svn.opensource.apple.com/repository/calendarserver/CalendarServer/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/conf/repository.xml
    CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.server.patch
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/directory.py
    CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py

Modified: CalendarServer/branches/users/cdaboo/dropbox/conf/repository-static.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/conf/repository-static.xml	2006-11-02 20:00:23 UTC (rev 353)
+++ CalendarServer/branches/users/cdaboo/dropbox/conf/repository-static.xml	2006-11-02 20:29:22 UTC (rev 354)
@@ -51,7 +51,7 @@
             </acl>
           </properties>
           <members>
-            <collection name="users" tag="principals">
+            <collection name="users" tag="principals" account="yes">
               <pytype>twistedcaldav.static.CalendarPrincipalCollectionFile</pytype>
               <properties>
                 <acl>

Modified: CalendarServer/branches/users/cdaboo/dropbox/conf/repository.dtd
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/conf/repository.dtd	2006-11-02 20:00:23 UTC (rev 353)
+++ CalendarServer/branches/users/cdaboo/dropbox/conf/repository.dtd	2006-11-02 20:29:22 UTC (rev 354)
@@ -23,7 +23,9 @@
 
     <!ELEMENT collection (pytype?, params?, properties, members)>
       <!ATTLIST collection name CDATA ""
-                             tag (none|principals|calendars) "none">
+                             tag (none|principals|calendars) "none"
+                             account (yes|no) "no"
+                             initialize (yes|no) "no">
       <!ELEMENT pytype     (#PCDATA)>
       <!ELEMENT params     (param*)>
         <!ELEMENT param    (key, value)>

Modified: CalendarServer/branches/users/cdaboo/dropbox/conf/repository.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/conf/repository.xml	2006-11-02 20:00:23 UTC (rev 353)
+++ CalendarServer/branches/users/cdaboo/dropbox/conf/repository.xml	2006-11-02 20:29:22 UTC (rev 354)
@@ -20,7 +20,7 @@
 
 <repository>
 
-  <docroot auto-principal-collection-set="no">
+  <docroot auto-principal-collection-set="yes">
     <collection>
       <pytype>twisted.web2.dav.static.DAVFile</pytype>
       <properties>
@@ -37,13 +37,6 @@
             <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/users/</href><href>/principals/groups/</href><href>/principals/resources/</href></principal-collection-set></prop>
       </properties>
       <members>
         <!--
@@ -124,7 +117,7 @@
             </collection>
           </members>
         </collection>
-        <collection name="principals" tag="principals">
+        <collection name="principals" initialize="yes">
           <pytype>twistedcaldav.directory.DirectoryPrincipalProvisioningResource</pytype>
           <params>
             <param>
@@ -142,7 +135,7 @@
             </acl>
           </properties>
           <members>
-            <collection name="users">
+            <collection name="users" tag="principals">
               <pytype>twistedcaldav.directory.DirectoryUserPrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
@@ -155,7 +148,7 @@
               </properties>
               <members/>
             </collection>
-            <collection name="groups">
+            <collection name="groups" tag="principals">
               <pytype>twistedcaldav.directory.DirectoryGroupPrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
@@ -168,7 +161,7 @@
               </properties>
               <members/>
             </collection>
-            <collection name="resources">
+            <collection name="resources" tag="principals">
               <pytype>twistedcaldav.directory.DirectoryResourcePrincipalProvisioningResource</pytype>
               <properties>
                 <acl>

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-02 20:00:23 UTC (rev 353)
+++ CalendarServer/branches/users/cdaboo/dropbox/lib-patches/Twisted/twisted.web2.server.patch	2006-11-02 20:29:22 UTC (rev 354)
@@ -2,16 +2,23 @@
 ===================================================================
 --- twisted/web2/server.py	(revision 18545)
 +++ twisted/web2/server.py	(working copy)
-@@ -1,6 +1,8 @@
- # -*- test-case-name: twisted.web2.test.test_server -*-
- # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
- # See LICENSE for details.
+@@ -19,6 +19,7 @@
+ from zope.interface import implements
+ # Twisted Imports
+ from twisted.internet import defer
 +from twisted.internet.defer import succeed
+ from twisted.python import log, failure
+ 
+ # Sibling Imports
+@@ -26,6 +27,7 @@
+ from twisted.web2 import http_headers
+ from twisted.web2.filter.range import rangefilter
+ from twisted.web2 import error
 +from twisted.web2.dav.util import joinURL
  
- 
- """This is a web-sever which integrates with the twisted.internet
-@@ -150,17 +152,32 @@
+ from twisted.web2 import version as web2_version
+ from twisted import __version__ as twisted_version
+@@ -150,6 +152,9 @@
              self._initialprepath = kw['prepathuri']
              del kw['prepathuri']
  
@@ -21,6 +28,7 @@
          # Copy response filters from the class
          self.responseFilters = self.responseFilters[:]
          self.files = {}
+@@ -156,11 +161,23 @@
          self.resources = []
          http.Request.__init__(self, *args, **kw)
  
@@ -51,7 +59,7 @@
          
          d = defer.Deferred()
          d.addCallback(self._getChild, self.site.resource, self.postpath)
-+        d.addCallback(self._rememberResource, "/" + "/".join(self.prepath))
++        d.addCallback(self._rememberResource, "/" + "/".join(self.postpath))
          d.addCallback(lambda res, req: res.renderHTTP(req), self)
          d.addCallback(self._cbFinishRender)
          d.addErrback(self._processingFailed)
@@ -98,22 +106,21 @@
  
      def locateResource(self, url):
          """
-@@ -385,8 +397,13 @@
+@@ -385,7 +397,12 @@
              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
-         #
-@@ -406,19 +423,62 @@
+@@ -406,10 +423,50 @@
                  "URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
              ))
  
@@ -122,20 +129,18 @@
          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
++
++        def notFound(f):
++            f.trap(http.HTTPError)
++            if f.response.code != responsecode.NOT_FOUND:
 +                return f
-             return None
- 
--        return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
++            return None
++
 +        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
@@ -165,18 +170,22 @@
 +        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:
+ 
+         def notFound(f):
+             f.trap(http.HTTPError)
+@@ -414,10 +471,13 @@
+         def notFound(f):
+             f.trap(http.HTTPError)
+             if f.response.code != responsecode.NOT_FOUND:
+-                raise f
 +                return f
-+            return None
-+
+             return None
+ 
+-        return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
 +        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/twistedcaldav/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/directory.py	2006-11-02 20:00:23 UTC (rev 353)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/directory.py	2006-11-02 20:29:22 UTC (rev 354)
@@ -28,7 +28,7 @@
     "DirectoryPrincipalProvisioningResource",
 ]
 
-from zope.interface import implements
+from zope.interface import implements, Attribute, Interface
 
 from twisted.cred import checkers, credentials, error
 from twisted.cred.credentials import UsernamePassword
@@ -58,6 +58,122 @@
 import os
 import unicodedata
 
+class IDirectoryService(Interface):
+    """
+    Directory Service
+    """
+    def recordTypes():
+        """
+        @return: a sequence of strings denoting the record types that are kept
+            in the directory.  For example: C{["users", "groups", "resources"]}.
+        """
+
+    def listRecords(type):
+        """
+        @param type: the type of records to retrieve.
+        @return: an iterable of records of the given type.
+        """
+
+class IDirectoryRecord(Interface):
+    """
+    Directory Record
+    """
+    directory             = Attribute("The L{IDirectoryService} this record exists in.")
+    recordType            = Attribute("The type of this record.")
+    guid                  = Attribute("The GUID of this record.")
+    shortName             = Attribute("The name of this record.")
+    fullName              = Attribute("The full name of this record.")
+    calendarUserAddresses = Attribute("A sequence of calendar user addresses of this record.")
+
+    def authenticate(credentials):
+        """
+        Verify that the given credentials can authenticate the principal
+        represented by this record.
+        @param credentials: the credentials to authenticate with.
+        @return: C{True} if the given credentials match this record,
+            C{False} otherwise.
+        """
+
+class DirectoryRecord(object):
+    implements(IDirectoryRecord)
+
+    def __init__(self, directory, recordType, guid, shortName, fullName=None, calendarUserAddresses=()):
+        self.directory             = directory
+        self.recordType            = recordType
+        self.guid                  = guid
+        self.shortName             = shortName
+        self.fullName              = fullName
+        self.calendarUserAddresses = calendarUserAddresses
+
+    def authenticate(credentials):
+        return False
+
+class OpenDirectoryService(object):
+    """
+    Open Directory implementation of L{IDirectoryService}.
+    """
+    implements(IDirectoryService)
+
+    def __init__(self, node="/Search"):
+        directory = opendirectory.odInit(node)
+        if directory is None:
+            raise ValueError("Failed to open Open Directory Node: %s" % (node,))
+
+        self._directory = directory
+
+    def recordTypes(self):
+        return ("users", "groups", "resources")
+
+    def listRecords(self, recordType):
+        def makeRecord(shortName, guid, lastModified, principalURI):
+            if not guid:
+                return None
+
+            ##
+            # FIXME: Also verify that principalURI is on this server
+            # Which probably means that the host information needs to be on
+            # the site object, and that we need the site object passed to
+            # __init__() here.
+            ##
+
+            return OpenDirectoryRecord(
+                directory = self,
+                recordType = recordType,
+                guid = guid,
+                shortName = shortName,
+                fullName = None,
+                calendarUserAddresses = (),
+            )
+
+        if recordType == "users":
+            for data in opendirectory.listUsers(self._directory):
+                yield makeRecord(*data)
+            return
+
+        if recordType == "groups":
+            for data in opendirectory.listGroups(self._directory):
+                yield makeRecord(*data)
+            return
+
+        if recordType == "resources":
+            for data in opendirectory.listResources(self._directory):
+                yield makeRecord(*data)
+            return
+
+        raise AssertionError("Unknown Open Directory record type: %s" % (recordType,))
+
+class OpenDirectoryRecord(DirectoryRecord):
+    """
+    Open Directory implementation of L{IDirectoryRecord}.
+    """
+    def authenticate(self, credentials):
+        if isinstance(credentials, credentials.UsernamePassword):
+            return opendirectory.authenticateUser(self.directory, self.shortName, credentials.password)
+
+        return False
+
+######################
+
 class DirectoryCredentialsChecker (TwistedPropertyChecker):
 
     def requestAvatarId(self, credentials):
@@ -173,10 +289,6 @@
         return self.getPropertyValue(customxml.TwistedGUIDProperty)
     
     def readProperty(self, property, request):
-        """
-        Override inherited behavior to make calendar-user-address-set property 'protected'/'live'
-        """
-
         if type(property) is tuple:
             qname = property
         else:
@@ -184,35 +296,21 @@
 
         namespace, name = qname
 
-        # Only return the calendar prinicpal URI when calendar-user-address-set is requested.
         if namespace == caldavxml.caldav_namespace:
             if name == "calendar-user-address-set":
-                return succeed(caldavxml.CalendarUserAddressSet(davxml.HRef().fromString(self.getPropertyValue(customxml.TwistedCalendarPrincipalURI))))
+                return succeed(caldavxml.CalendarUserAddressSet(
+                    *[davxml.HRef().fromString(uri) for uri in self.calendarUserAddresses()]
+                ))
 
         return super(DirectoryPrincipalFile, self).readProperty(qname, request)
 
     def writeProperty(self, property, request):
-        """
-        Override inherited behavior to make calendar-user-address-set property 'protected'/'live'
-        """
+        # This resource is read-only.
+        raise HTTPError(StatusResponse(
+            responsecode.FORBIDDEN,
+            "Protected property %s may not be set." % (property.sname(),)
+        ))
 
-        if type(property) is tuple:
-            qname = property
-        else:
-            qname = property.qname()
-
-        namespace, name = qname
-
-        # Don't allow changes to the calendar-user-address set as the value comes from the directory.
-        if namespace == caldavxml.caldav_namespace:
-            if name == "calendar-user-address-set":
-                raise HTTPError(StatusResponse(
-                    responsecode.FORBIDDEN,
-                    "Protected property %s may not be set." % (property.sname(),)
-                ))
-
-        return super(DirectoryPrincipalFile, self).readProperty(qname, request)
-
     def calendarUserAddresses(self):
         # Must have a valid calendar principal uri
         if self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):

Modified: CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py
===================================================================
--- CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py	2006-11-02 20:00:23 UTC (rev 353)
+++ CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/repository.py	2006-11-02 20:29:22 UTC (rev 354)
@@ -89,6 +89,8 @@
 
 ATTRIBUTE_NAME = "name"
 ATTRIBUTE_TAG = "tag"
+ATTRIBUTE_ACCOUNT = "account"
+ATTRIBUTE_INITIALIZE = "initialize"
 
 ATTRVALUE_NONE = "none"
 ATTRVALUE_PRINCIPALS = "principals"
@@ -302,7 +304,12 @@
         
         self.docRoot.build()
         if self.doAccounts:
-            self.accounts.provision(self.docRoot.principalCollection, self.docRoot.calendarHome, self.resetACLs)
+            self.accounts.provision(
+                self.docRoot.principalCollections,
+                self.docRoot.accountCollection,
+                self.docRoot.initCollections,
+                self.docRoot.calendarHome,
+                self.resetACLs)
             
         # Handle global quota value
         CalendarHomeFile.quotaLimit = self.quota
@@ -331,7 +338,9 @@
         """
         self.collection = None
         self.path = docroot
-        self.principalCollection = None
+        self.principalCollections = []
+        self.accountCollection = None
+        self.initCollections = []
         self.calendarHome = None
         self.autoPrincipalCollectionSet = True
         
@@ -358,12 +367,15 @@
         # Setup the principal-collection-set property if required
         if self.autoPrincipalCollectionSet:
             # Check that a principal collection was actually created and 'tagged'
-            if self.principalCollection is None:
+            if not self.principalCollections:
                 log.msg("Cannot create a DAV:principal-collection-set property on the root resource because there are no principal collections.")
                 return
             
             # Create the private property
-            pcs = davxml.PrincipalCollectionSet(davxml.HRef.fromString(self.principalCollection.uri))
+            hrefs = []
+            for collection in self.principalCollections:
+                hrefs.append(davxml.HRef.fromString(collection.uri))
+            pcs = davxml.PrincipalCollectionSet(*hrefs)
             self.collection.resource.writeDeadProperty(pcs)
 
 class Collection (object):
@@ -391,9 +403,13 @@
         if node.hasAttribute(ATTRIBUTE_TAG):
             tag = node.getAttribute(ATTRIBUTE_TAG)
             if tag == ATTRVALUE_PRINCIPALS:
-                builder.principalCollection = self
+                builder.principalCollections.append(self)
             elif tag == ATTRVALUE_CALENDARS:
                 builder.calendarHome = self
+        if node.hasAttribute(ATTRIBUTE_ACCOUNT) and node.getAttribute(ATTRIBUTE_ACCOUNT) == ATTRIBUTE_VALUE_YES:
+            builder.accountCollection = self
+        if node.hasAttribute(ATTRIBUTE_INITIALIZE) and node.getAttribute(ATTRIBUTE_INITIALIZE) == ATTRIBUTE_VALUE_YES:
+            builder.initCollections.append(self)
         
         for child in node._get_childNodes():
             if child._get_localName() == ELEMENT_PYTYPE:
@@ -644,7 +660,9 @@
 
     def __init__(self):
         self.items = []
-        self.principalCollection = None
+        self.principalCollections = None
+        self.accountCollection = None
+        self.initCollections = None
         self.calendarHome = None
         
     def parseXML( self, node ):
@@ -663,24 +681,34 @@
                 principal.parseXML( child )
                 self.items.append((repeat, principal))
     
-    def provision(self, principalCollection, calendarHome, resetACLs):
+    def provision(self, principalCollections, accountCollection, initCollections, calendarHome, resetACLs):
         """
         Carry out provisioning operation.
-        @param principalCollection: the L{Collection} of the principal collection in which to
+        @param principalCollections: a C{list} of L{Collection}'s for the principal collections.
+        @param accountCollection: the L{Collection} of the principal collection in which to
             create user principals.
-        @param calendarHome:        the L{Collection} for the calendar home of principals.
-        @param resetACLs:           if True, ACL privileges on all resources related to the
+        @param initCollections: a C{list} of L{Collection}'s for the principal collections to be initialized.
+        @param calendarHome:  the L{Collection} for the calendar home of principals.
+        @param resetACLs: if True, ACL privileges on all resources related to the
             accounts being created are reset, if False no ACL privileges are changed.
         """
-        self.principalCollection = principalCollection
+        self.principalCollections = principalCollections
+        self.accountCollection = accountCollection
+        self.initCollections = initCollections
         self.calendarHome = calendarHome
 
-        if self.calendarHome is not None:
-            self.principalCollection.resource.initialize(
-                self.calendarHome.uri,
-                self.calendarHome.resource,
-            )
+        if self.initCollections and self.calendarHome is not None:
+            for collection in self.initCollections:
+                collection.resource.initialize(
+                    self.calendarHome.uri,
+                    self.calendarHome.resource,
+                )
 
+        # Check for proper account home
+        if not self.accountCollection:
+            log.err("Accounts cannot be created: no principal collection was marked with an account attribute.")
+            raise ValueError, "Accounts cannot be created."
+
         # Provision each user
         for repeat, principal in self.items:
             if repeat == 1:
@@ -696,10 +724,10 @@
         @param resetACLs: if True, ACL privileges on all resources related to the
             accounts being created are reset, if False no ACL privileges are changed.
         """
-        principalURL = joinURL(self.principalCollection.uri, item.uid)
+        principalURL = joinURL(self.accountCollection.uri, item.uid)
 
         # Create principal resource
-        principal = FilePath(os.path.join(self.principalCollection.resource.fp.path, item.uid))
+        principal = FilePath(os.path.join(self.accountCollection.resource.fp.path, item.uid))
         principal_exists = principal.exists()
         if not principal_exists:
             principal.open("w").close()

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


More information about the calendarserver-changes mailing list