[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