[CalendarServer-changes] [358] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Thu Nov 2 17:40:54 PST 2006
Revision: 358
http://trac.macosforge.org/projects/calendarserver/changeset/358
Author: wsanchez at apple.com
Date: 2006-11-02 17:40:54 -0800 (Thu, 02 Nov 2006)
Log Message:
-----------
Reorg: split directory.py into a module and break it up into parts.
Modified Paths:
--------------
CalendarServer/trunk/conf/caldavd-dev.plist
CalendarServer/trunk/conf/repository-proxy.xml
CalendarServer/trunk/conf/repository.xml
CalendarServer/trunk/twistedcaldav/repository.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/directory/
CalendarServer/trunk/twistedcaldav/directory/__init__.py
CalendarServer/trunk/twistedcaldav/directory/cred.py
CalendarServer/trunk/twistedcaldav/directory/directory.py
CalendarServer/trunk/twistedcaldav/directory/idirectory.py
CalendarServer/trunk/twistedcaldav/directory/opendirectory.py
CalendarServer/trunk/twistedcaldav/directory/resource.py
Removed Paths:
-------------
CalendarServer/trunk/twistedcaldav/directory.py
Modified: CalendarServer/trunk/conf/caldavd-dev.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-dev.plist 2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/conf/caldavd-dev.plist 2006-11-03 01:40:54 UTC (rev 358)
@@ -27,7 +27,7 @@
<true/>
<key>DocumentRoot</key>
- <string>twistedcaldav/test/data/</string>
+ <string>/Library/CalendarServer/Documents</string>
<key>Port</key>
<integer>8008</integer>
@@ -42,25 +42,22 @@
<integer>8443</integer>
<key>SSLPrivateKey</key>
- <string>conf/server.pem</string>
+ <string>/etc/certificates/Default.key</string>
<key>SSLCertificate</key>
- <string>conf/server.pem</string>
+ <string>/etc/certificates/Default.crt</string>
- <key>ManholePort</key>
- <integer>8000</integer>
-
<key>ServerLogFile</key>
- <string>bin/server.log</string>
+ <string>/var/log/caldavd/server.log</string>
<key>ErrorLogFile</key>
- <string>bin/error.log</string>
+ <string>/var/log/caldavd/error.log</string>
<key>PIDFile</key>
- <string>bin/twistd-caldavd.pid</string>
+ <string>/var/log/caldavd/caldavd.pid</string>
<key>Repository</key>
- <string>conf/repository-dev.xml</string>
+ <string>/etc/caldavd/repository.xml</string>
<key>CreateAccounts</key>
<true/>
@@ -69,11 +66,8 @@
<true/>
<key>twistdLocation</key>
- <string>../Twisted/bin/twistd</string>
+ <string>/usr/share/caldavd/bin/twistd</string>
- <key>EventExpirationDays</key>
- <integer>0</integer>
-
<key>MaximumAttachmentSizeBytes</key>
<integer>1048576</integer><!-- 1Mb -->
Modified: CalendarServer/trunk/conf/repository-proxy.xml
===================================================================
--- CalendarServer/trunk/conf/repository-proxy.xml 2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/conf/repository-proxy.xml 2006-11-03 01:40:54 UTC (rev 358)
@@ -118,7 +118,7 @@
</members>
</collection>
<collection name="principals" initialize="yes">
- <pytype>twistedcaldav.directory.DirectoryPrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
<params>
<param>
<key>DirectoryNode</key>
@@ -136,7 +136,7 @@
</properties>
<members>
<collection name="users" tag="principals">
- <pytype>twistedcaldav.directory.DirectoryUserPrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
<properties>
<acl>
<ace>
@@ -149,7 +149,7 @@
<members/>
</collection>
<collection name="groups" tag="principals">
- <pytype>twistedcaldav.directory.DirectoryGroupPrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
<properties>
<acl>
<ace>
@@ -162,7 +162,7 @@
<members/>
</collection>
<collection name="resources" tag="principals">
- <pytype>twistedcaldav.directory.DirectoryResourcePrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
<properties>
<acl>
<ace>
Modified: CalendarServer/trunk/conf/repository.xml
===================================================================
--- CalendarServer/trunk/conf/repository.xml 2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/conf/repository.xml 2006-11-03 01:40:54 UTC (rev 358)
@@ -118,7 +118,7 @@
</members>
</collection>
<collection name="principals" initialize="yes">
- <pytype>twistedcaldav.directory.DirectoryPrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
<params>
<param>
<key>DirectoryNode</key>
@@ -136,7 +136,7 @@
</properties>
<members>
<collection name="users" tag="principals">
- <pytype>twistedcaldav.directory.DirectoryUserPrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
<properties>
<acl>
<ace>
@@ -149,7 +149,7 @@
<members/>
</collection>
<collection name="groups" tag="principals">
- <pytype>twistedcaldav.directory.DirectoryGroupPrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
<properties>
<acl>
<ace>
@@ -162,7 +162,7 @@
<members/>
</collection>
<collection name="resources" tag="principals">
- <pytype>twistedcaldav.directory.DirectoryResourcePrincipalProvisioningResource</pytype>
+ <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
<properties>
<acl>
<ace>
Added: CalendarServer/trunk/twistedcaldav/directory/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/__init__.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/__init__.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,28 @@
+##
+# 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: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+"""
+Calendar server directory service module.
+"""
+
+__all__ = [
+ "directory",
+ "idirectory",
+ "opendirectory",
+ "resource",
+]
Copied: CalendarServer/trunk/twistedcaldav/directory/cred.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/cred.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/cred.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,57 @@
+##
+# 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
+##
+
+"""
+Implements a directory-backed principal hierarchy.
+"""
+
+__all__ = [
+ "DirectoryCredentialsChecker",
+]
+
+from twisted.internet.defer import succeed
+from twisted.cred.error import UnauthorizedLogin
+from twisted.web2.dav.auth import IPrincipalCredentials
+from twisted.web2.dav.auth import TwistedPropertyChecker
+
+from twistedcaldav import customxml
+
+class DirectoryCredentialsChecker (TwistedPropertyChecker):
+ def __init__(self, service):
+ """
+ @param service: an L{IDirectoryService} provider.
+ """
+ self.service = service
+
+ def requestAvatarId(self, credentials):
+ # If there is no calendar principal URI then the calendar user is disabled.
+ credentials = IPrincipalCredentials(credentials)
+ if not credentials.authnPrincipal.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
+ # Try regular password check
+ return TwistedPropertyChecker.requestAvatarId(self, credentials)
+
+ user = self.service.findUserWithShortName(credentials.credentials.username)
+ raise UnauthorizedLogin("Unknown credentials type for principal: %s" % (credentials.authnURI,))
+
+ if not user:
+ raise UnauthorizedLogin("No such user: %s" % (user,))
+
+ if user.authenticate(credentials.credentials):
+ return succeed((credentials.authnURI, credentials.authzURI))
+ else:
+ raise UnauthorizedLogin("Incorrect credentials for user: %s" % (user,))
Copied: CalendarServer/trunk/twistedcaldav/directory/directory.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,46 @@
+##
+# 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
+##
+
+"""
+Generic directory service classes.
+"""
+
+__all__ = [
+ "DirectoryRecord",
+]
+
+from zope.interface import implements
+
+from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
+
+class DirectoryService(object):
+ implements(IDirectoryService)
+
+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
Copied: CalendarServer/trunk/twistedcaldav/directory/idirectory.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/idirectory.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/idirectory.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,64 @@
+##
+# 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: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+"""
+Directory service interfaces.
+"""
+
+__all__ = [
+ "IDirectoryService",
+ "IDirectoryRecord",
+]
+
+from zope.interface import Attribute, Interface
+
+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.
+ """
Copied: CalendarServer/trunk/twistedcaldav/directory/opendirectory.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectory.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectory.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,92 @@
+##
+# 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
+##
+
+"""
+Apple Open Directory implementation.
+"""
+
+__all__ = [
+ "OpenDirectoryService",
+ "OpenDirectoryRecord",
+]
+
+import opendirectory
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+
+class OpenDirectoryService(DirectoryService):
+ """
+ Open Directory implementation of L{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
Copied: CalendarServer/trunk/twistedcaldav/directory/resource.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/resource.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/resource.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,982 @@
+##
+# 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
+##
+
+"""
+Implements a directory-backed principal hierarchy.
+"""
+
+__all__ = [
+ "DirectoryPrincipalFile",
+ "DirectoryUserPrincipalProvisioningResource",
+ "DirectoryGroupPrincipalProvisioningResource",
+ "DirectoryResourcePrincipalProvisioningResource",
+ "DirectoryPrincipalProvisioningResource",
+]
+
+from twisted.python import log
+from twisted.internet import reactor
+from twisted.internet import task
+from twisted.internet.defer import succeed
+from twisted.cred import credentials
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.static import DAVFile
+from twisted.web2.dav.util import joinURL
+from twisted.web2.http import HTTPError
+from twisted.web2.http import StatusResponse
+
+from twistedcaldav import caldavxml
+from twistedcaldav import customxml
+from twistedcaldav.principalindex import GroupIndex
+from twistedcaldav.principalindex import ResourceIndex
+from twistedcaldav.principalindex import UserIndex
+from twistedcaldav.resource import CalendarPrincipalCollectionResource
+from twistedcaldav.static import CalendarPrincipalFile
+
+import dsattributes
+import opendirectory
+import os
+import unicodedata
+
+class DirectoryPrincipalFile (CalendarPrincipalFile):
+ """
+ Directory principal resource.
+ """
+ def __init__(self, parent, path, url):
+ """
+ @param path: the path to the file which will back the resource.
+ @param url: the primary URL for the resource. This is the url which
+ will be returned by L{principalURL}.
+ """
+ super(DirectoryPrincipalFile, self).__init__(path, url)
+
+ self._parent = parent
+
+ def checkCredentials(self, creds):
+ """
+ Check whether the provided credentials can be used to authenticate this prinicpal.
+
+ @param creds: the L{ICredentials} for testing.
+ @return: True if the credentials match, False otherwise
+ """
+
+ # If there is no calendar principal URI then the calendar user is disabled.
+ if not self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
+ return False
+
+ if isinstance(creds, credentials.UsernamePassword):
+ return opendirectory.authenticateUser(self._parent.directory, self.fp.basename(), creds.password)
+ else:
+ return False
+
+ def directory(self):
+ """
+ Get the directory object used for directory operations.
+
+ @return: C{object} for the directory instance
+ """
+
+ return self._parent.directory
+
+ def groupMembers(self):
+ """
+ See L{IDAVPrincipalResource.groupMembers}.
+ """
+
+ # Check for the list of group member GUIDs
+ if self.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
+ # Get the list of GUIDs from the WebDAV private property
+ memberguids = self.readDeadProperty(customxml.TwistedGroupMemberGUIDs())
+ guids = [str(e) for e in memberguids.children]
+
+ # Try to find each GUID in collections
+ result = []
+ for guid in guids:
+ uri = DirectoryTypePrincipalProvisioningResource.findAnyGUID(guid)
+ if uri is not None:
+ result.append(uri)
+
+ return result
+
+ return ()
+
+ def groupMemberships(self):
+ """
+ See L{IDAVPrincipalResource.groupMemberships}.
+ """
+
+ # Find any groups that match this user's GUID
+ guid = self.getGUID()
+ return DirectoryTypePrincipalProvisioningResource.findAnyGroupGUID(guid)
+
+ def getPropertyValue(self, cls):
+ """
+ Get the requested proeprty value or return empty string.
+ """
+
+ if self.hasDeadProperty(cls()):
+ prop = self.readDeadProperty(cls())
+ return str(prop)
+ else:
+ return ""
+
+ def setPropertyValue(self, str, cls):
+ """
+ Set the requested property value or remove it if the value is empty.
+ """
+
+ if str:
+ self.writeDeadProperty(cls.fromString(str))
+ else:
+ self.removeDeadProperty(cls())
+
+ def getGUID(self):
+ return self.getPropertyValue(customxml.TwistedGUIDProperty)
+
+ def readProperty(self, property, request):
+ if type(property) is tuple:
+ qname = property
+ else:
+ qname = property.qname()
+
+ namespace, name = qname
+
+ if namespace == caldavxml.caldav_namespace:
+ if name == "calendar-user-address-set":
+ 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):
+ # This resource is read-only.
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Protected property %s may not be set." % (property.sname(),)
+ ))
+
+ def calendarUserAddresses(self):
+ # Must have a valid calendar principal uri
+ if self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
+ return (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI),)
+ else:
+ # If there is no calendar principal URI then the calendar user is disabled so do not provide
+ # a valid calendar address.
+ return ()
+
+ def matchesCalendarUserAddress(self, request, address):
+ # By default we will always allow either a relative or absolute URI to the principal to
+ # be supplied as a valid calendar user address.
+
+ # Try calendar principal URI
+ return self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI) and (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI) == address)
+
+ def enable(self, calhome, enable):
+ """
+ Enable or disable this principal and access to any calendars owned by it.
+
+ @param calhome: L{DAVFile} for the container of the calendar home of this user.
+ @param enable: C{True} to enable, C{False} to disable.
+ """
+ # Get home collection resource
+ calrsrc = calhome.getChild(self.principalUID())
+
+ # Handle access for the calendar home
+ if enable:
+ calrsrc.disable(False)
+ else:
+ calrsrc.disable(True)
+
+ def remove(self, calhome):
+ """
+ Remove this principal by hiding (archiving) any calendars owned by it. This is done
+ by turning on the disabling and renaming the calendar home to ensure a future user
+ with the same id won't see the old calendars.
+
+ @param calhome: L{DAVFile} for the container of the calendar home of this user.
+ """
+
+ # Get home collection resource
+ calrsrc = calhome.getChild(self.principalUID())
+
+ # Disable access for the calendar home
+ calrsrc.disable(True)
+
+ # Rename the calendar home to the original name with the GUID appended
+ newname = self.principalUID() + "-" + self.getPropertyValue(customxml.TwistedGUIDProperty)
+
+ try:
+ # Make sure the new name is not already in use
+ if os.path.exists(newname):
+ count = 1
+ tempname = newname + "-%d"
+ while(os.path.exists(tempname % count)):
+ count += 1
+ newname = tempname % count
+ os.rename(calrsrc.fp.path, calrsrc.fp.sibling(newname).path)
+ except OSError:
+ log.msg("Directory: Failed to rename %s to %s when deleting a principal" %
+ (calrsrc.fp.path, calrsrc.fp.sibling(newname).path))
+
+ # Remove the disabled property to prevent lock out in the future
+ calrsrc.disable(False)
+
+class DirectoryTypePrincipalProvisioningResource (CalendarPrincipalCollectionResource, DAVFile):
+ """
+ L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+ as needed.
+
+ This includes a periodic task for refreshing the cached data.
+ """
+ periodicSyncIntervalSeconds = 60.0
+
+ typeUnknown = 0
+ typeUser = 1
+ typeGroup = 2
+ typeResource = 3
+
+ def __init__(self, path, url):
+ """
+ @param path: the path to the file which will back the resource.
+ @param url: the primary URL for the resource. Provisioned child
+ resources will use a URL based on C{url} as their primary URLs.
+ @param directory: the reference to the directory to use
+ """
+ CalendarPrincipalCollectionResource.__init__(self, url)
+ DAVFile.__init__(self, path)
+ self.directory = None
+ self.calendarhomeroot = None
+ self.index = None
+ self.type = DirectoryTypePrincipalProvisioningResource.typeUnknown
+
+ def setup(self, directory):
+ self.directory = directory
+
+ def initialize(self, homeuri, home):
+ """
+ May be called during repository account initialization.
+
+ @param homeuri: C{str} uri of the calendar home root.
+ @param home: L{DAVFile} of the calendar home root.
+ """
+
+ # Make sure index is valid and sync with directory
+ self.index.check()
+ self.calendarhomeroot = (homeuri, home)
+
+ #
+ # There is a problem with the interaction of Directory Services and the
+ # fork/fork process the server goes through to daemonize itself. For some
+ # resaon, if DS is used before the fork, then calls to it afterwards all
+ # return eServerSendError (-14740) errors.
+ #
+ # To get around this we must not use opendirectory module calls here, as this
+ # method gets run before the fork/fork. So instead, we schedule a sync
+ # operation to occur one second after the reactor starts up - which is after
+ # the fork/fork. The problem with this is that the http server is already up
+ # and running at that point BEFORE any initially provisioning is done.
+ #
+
+ # Create a periodic sync operation to keep the cached user list
+ # in sync with the directory server.
+ def periodicSync(self):
+ self.syncNames()
+
+ # Add initial sync operation
+ reactor.callLater(1.0, periodicSync, self) #@UndefinedVariable
+
+ # Add periodic sync operations
+ runner = task.LoopingCall(periodicSync, self)
+ runner.start(DirectoryTypePrincipalProvisioningResource.periodicSyncIntervalSeconds, now=False)
+
+ def listNames(self):
+ """
+ List all the names currently in the directory.
+
+ @return: C{list} containing C{str}'s for each name found, or C{None} if failed.
+ """
+ raise NotImplementedError
+
+ def listIndexAttributes(self):
+ """
+ List all the names currently in the directory with specific attributes needed for indexing.
+
+ @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+ The C{tuple} elements are: uid, guid, last-modified.
+ """
+ raise NotImplementedError
+
+ def listCommonAttributes(self, names):
+ """
+ List specified names currently in the directory returning useful attributes.
+
+ @param names: C{list} of record entries to list attributes for.
+ @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+ or C{None} on failure.
+ """
+ raise NotImplementedError
+
+ def validName(self, name):
+ """
+ Verify that the supplied name exists as an entry in the directory and that the
+ name corresponds to one that can use the calendar.
+
+ @param name: C{str} of the name to check.
+ @return: C{True} if the name exists, C{False} otherwise.
+ """
+ raise NotImplementedError
+
+ def directoryAttributes(self, name):
+ """
+ Return the attributes relevant to the directory entry.
+
+ @param name: C{str} containing the name for the directory entry.
+ @return: C{dict} containing the attribute key/value map.
+ """
+ raise NotImplementedError
+
+ def syncNames(self):
+ """
+ Synchronize the data in the directory with the local cache of resources in the file system.
+ """
+ #log.msg("Directory: Synchronizing cache for %s" % (self.getTitle(),))
+
+ # Get index entries from directory and from cache
+ remoteindex = self.listIndexAttributes()
+ localindex = self.index.listIndex()
+
+ # Create dicts indexed by GUID for each
+ remotedict = {}
+ for entry in remoteindex:
+ remotedict[entry[dsattributes.indexGUID]] = entry
+ localdict = {}
+ for entry in localindex:
+ localdict[entry[dsattributes.indexGUID]] = entry
+
+ # Extract list of GUIDs in each
+ remoteguids = [entry[dsattributes.indexGUID] for entry in remoteindex]
+ localguids = [entry[dsattributes.indexGUID] for entry in localindex]
+
+ remoteguidset = set(remoteguids)
+ remoteguids = None
+ localguidset = set(localguids)
+ localguids = None
+
+ new_remote = list(remoteguidset.difference(localguidset))
+ removed_remote = list(localguidset.difference(remoteguidset))
+
+ # Remove old principals
+ old_names = [localdict[guid][dsattributes.indexUID] for guid in removed_remote]
+ old_names.sort()
+ for name in old_names:
+ self.removePrincipal(name, True)
+
+ # Get new ones (but only those with a CalendarPrincipalURI attribute)
+ new_names = [remotedict[guid][dsattributes.indexUID] for guid in new_remote if remotedict[guid][dsattributes.indexCalendarPrincipalURI]]
+
+ # Get all the directory entries for the new ones in one go for better performance
+ if new_names:
+ new_entries = self.listCommonAttributes(new_names)
+ new_names = [n for n in new_entries.iterkeys()]
+ new_names.sort()
+ for name in new_names:
+ self.addPrincipal(name, attrs=new_entries[name], fast=True)
+
+ # Look for changes in entries
+ common_entries = list(remoteguidset.intersection(localguidset))
+ for guid in common_entries:
+ old = localdict[guid]
+ new = remotedict[guid]
+ if old != new:
+ # Special case issue with unicode normalization of names
+ if ((old[dsattributes.indexUID] != new [dsattributes.indexUID]) and
+ (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
+ unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8"))) and
+ (old[1:] == new[1:])):
+ continue
+
+ self.changedPrincipal(old, new)
+
+ # Commit index after all changes are done
+ self.index.commit()
+
+ def addPrincipal(self, name, attrs=None, fast=False):
+ """
+ Add a new principal resource to the server.
+
+ @param name: C{str} containing the name of the resource to add.
+ @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
+ @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+ """
+ # This will create it
+ child_fp = self.fp.child(name)
+ assert not child_fp.exists()
+
+ assert self.exists()
+ assert self.isCollection()
+
+ child_fp.open("w").close()
+
+ # Now update the principal's cached data
+ self.updatePrincipal(name, attrs=attrs, fast=fast, new=True, nolog=True)
+
+ log.msg("Directory: Add %s to %s" % (name, self.getTitle()))
+
+ def changedPrincipal(self, old, new, fast=False):
+ """
+ Change a new principal resource to sync with directory.
+
+ @param old: C{str} containing the name of the original resource.
+ @param new: C{str} containing the name of the new resource.
+ @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+ """
+ # Look for change to calendar enabled state
+
+ # See if the name changed because that is a real pain!
+ if ((old[dsattributes.indexUID] != new[dsattributes.indexUID]) and
+ (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
+ unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8")))):
+ self.renamePrincipal(old[dsattributes.indexUID], new[dsattributes.indexUID])
+
+ # See if change in enable state
+ enable_state = old[dsattributes.indexCalendarPrincipalURI] != new[dsattributes.indexCalendarPrincipalURI]
+
+ # Do update (no log message if enable state is being changed as that will generate a log message itself)
+ self.updatePrincipal(new[dsattributes.indexUID], nolog=enable_state)
+
+ # Look for change in calendar enable state
+ if enable_state:
+ self.enablePrincipal(new[dsattributes.indexUID], len(new[dsattributes.indexCalendarPrincipalURI]) != 0)
+
+ def renamePrincipal(self, old, new):
+ """
+ Change a principal resource name to sync with directory.
+
+ @param old: C{str} containing the name of the original resource.
+ @param new: C{str} containing the name of the new resource.
+ """
+ log.msg("Directory: Renamed Principal %s to %s in %s" % (old, new, self.getTitle()))
+ raise NotImplementedError
+
+ def updatePrincipal(self, name, attrs = None, fast=False, new=False, nolog=False):
+ """
+ Update details about the named principal in the principal's own property store
+ and the principal collection index.
+
+ @param name: C{str} containing the principal name to update.
+ @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
+ @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+ @param new: C{True} when this update is the result of adding a new principal,
+ C{False} otherwise.
+ """
+ # Get attributes from directory
+ if attrs is None:
+ attrs = self.directoryAttributes(name)
+ realname = attrs.get(dsattributes.attrRealName, None)
+ guid = attrs.get(dsattributes.attrGUID, None)
+ lastModified = attrs.get(dsattributes.attrLastModified, None)
+ principalurl = attrs.get(dsattributes.attrCalendarPrincipalURI, None)
+
+ # Do provisioning (if the principal is a resource we will turn on the auto-provisioning option)
+ principal = self.getChild(name)
+ if principal is None:
+ log.msg("Directory: Failed to update missing principal: %s in %s" % (name, self.getTitle()))
+ return
+
+ if (new):
+ principal.provisionCalendarAccount(name, None, True, None, self.calendarhomeroot,
+ None, ["calendar"], self.type == DirectoryTypePrincipalProvisioningResource.typeResource)
+
+ # Add directory specific attributes to principal
+ principal.setPropertyValue(realname, davxml.DisplayName)
+ principal.setPropertyValue(guid, customxml.TwistedGUIDProperty)
+ principal.setPropertyValue(lastModified, customxml.TwistedLastModifiedProperty)
+ principal.setPropertyValue(principalurl, customxml.TwistedCalendarPrincipalURI)
+
+ # Special for group
+ if self.type == DirectoryTypePrincipalProvisioningResource.typeGroup:
+ # Get comma separated list of members and split into a list
+ groupmembers = attrs.get(dsattributes.attrGroupMembers, None)
+ if isinstance(groupmembers, list):
+ members = groupmembers
+ elif isinstance(groupmembers, str):
+ members = [groupmembers]
+ else:
+ members = []
+
+ # Create and write the group property
+ children = [customxml.TwistedGUIDProperty.fromString(s) for s in members]
+ principal.writeDeadProperty(customxml.TwistedGroupMemberGUIDs(*children))
+
+ # Do index
+ self.index.addPrincipal(name, principal, fast)
+
+ if not nolog:
+ log.msg("Directory: Updated %s in %s" % (name, self.getTitle()))
+
+ def enablePrincipal(self, name, enable):
+ """
+ Enable or disable calendar access for this principal.
+
+ @param enable: C{True} to enable, C{False} to disable
+ """
+ principal = self.getChild(name)
+ if principal is None:
+ log.msg("Directory: Failed to enable/disable missing principal: %s in %s" % (name, self.getTitle()))
+ return
+
+ if enable:
+ principal.enable(self.calendarhomeroot[1], True)
+ log.msg("Directory: Enabled %s in %s" % (name, self.getTitle()))
+ else:
+ principal.enable(self.calendarhomeroot[1], False)
+ log.msg("Directory: Disabled %s in %s" % (name, self.getTitle()))
+
+ def removePrincipal(self, name, fast=False):
+ """
+ Remove a principal from the cached resources.
+
+ @param name: C{str} containing the name of the principal to remove.
+ @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+ """
+
+ # Get the principal to 'hide' its calendars
+ principal = self.getChild(name)
+ if principal is None:
+ log.msg("Directory: Failed to remove missing principal: %s in %s" % (name, self.getTitle()))
+ else:
+ principal.remove(self.calendarhomeroot[1])
+
+ # Now remove the principal resource itself
+ child_fp = self.fp.child(name)
+ if child_fp.exists():
+ os.remove(child_fp.path)
+
+ # Do index
+ self.index.deleteName(name, fast)
+
+ log.msg("Directory: Delete %s from %s" % (name, self.getTitle()))
+
+ @classmethod
+ def findAnyGUID(clazz, guid):
+ """
+ Find the principal associated with the specified GUID.
+
+ @param guid: the C{str} containing the GUID to match.
+ @return: C{str} with matching principal URI, or C{None}
+ """
+ for url in clazz._principleCollectionSet.keys():
+ try:
+ pcollection = clazz._principleCollectionSet[url]
+ if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
+ principal = pcollection.findGUID(guid)
+ if principal is not None:
+ return principal
+ except ReferenceError:
+ pass
+
+ return None
+
+ def findGUID(self, guid):
+ """
+ See if a principal with the specified GUID exists and if so return its principal URI.
+
+ @param guid: the C{str} containing the GUID to match.
+ @return: C{str} with matching principal URI, or C{None}
+ """
+ name = self.index.nameFromGUID(guid)
+ if name is not None:
+ principal = self.getChild(name)
+ return principal.principalURL()
+ else:
+ return None
+
+ @classmethod
+ def findAnyGroupGUID(clazz, guid):
+ """
+ Find the principals containing the specified GUID as a group member.
+
+ @param guid: the C{str} containing the GUID to match.
+ @return: C{list} with matching principal URIs
+ """
+
+ result = []
+ for url in clazz._principleCollectionSet.keys():
+ try:
+ pcollection = clazz._principleCollectionSet[url]
+ if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
+ result.extend(pcollection.findGroupGUID(guid))
+ except ReferenceError:
+ pass
+
+ return result
+
+ def findGroupGUID(self, guid):
+ """
+ Find principals with the specified GUID as a group member.
+
+ @param guid: the C{str} containing the GUID to match.
+ @return: C{list} with matching principal URIs
+ """
+ # Only both for group collections
+ if self.type != DirectoryTypePrincipalProvisioningResource.typeGroup:
+ return []
+
+ result = []
+ for name in self.listChildren():
+ principal = self.getChild(name)
+ if principal.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
+ guids = principal.readDeadProperty(customxml.TwistedGroupMemberGUIDs)
+ for g in guids.children:
+ if str(g) == guid:
+ result.append(principal.principalURL())
+ break
+
+ return result
+
+ def isCollection(self):
+ """
+ See L{IDAVResource.isCollection}.
+ """
+ return True
+
+ def getChild(self, name):
+ """
+ Look up a child resource.
+ @return: the child of this resource with the given name.
+ """
+ if name == "":
+ return self
+
+ child = self.putChildren.get(name, None)
+ if child: return child
+
+ child_fp = self.fp.child(name)
+ if child_fp.exists():
+ return DirectoryPrincipalFile(self, child_fp.path, joinURL(self._url, name))
+ else:
+ return None
+
+ def principalSearchPropertySet(self):
+ """
+ See L{IDAVResource.principalSearchPropertySet}.
+ """
+ return davxml.PrincipalSearchPropertySet(
+ davxml.PrincipalSearchProperty(
+ davxml.PropertyContainer(
+ davxml.DisplayName()
+ ),
+ davxml.Description(
+ davxml.PCDATAElement("Display Name"),
+ **{"xml:lang":"en"}
+ ),
+ ),
+ davxml.PrincipalSearchProperty(
+ davxml.PropertyContainer(
+ caldavxml.CalendarUserAddressSet()
+ ),
+ davxml.Description(
+ davxml.PCDATAElement("Calendar User Addresses"),
+ **{"xml:lang":"en"}
+ ),
+ ),
+ )
+
+ def createSimilarFile(self, path):
+ if path == self.fp.path:
+ return self
+ else:
+ # TODO: Fix this - not sure how to get URI for second argument of __init__
+ return CalendarPrincipalFile(path, "")
+
+ def http_PUT (self, request): return responsecode.FORBIDDEN
+ def http_MKCOL (self, request): return responsecode.FORBIDDEN
+ def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
+
+class DirectoryUserPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
+ """
+ L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+ as needed.
+ """
+ def __init__(self, path, url):
+ """
+ @param path: the path to the file which will back the resource.
+ @param url: the primary URL for the resource. Provisioned child
+ resources will use a URL based on C{url} as their primary URLs.
+ @param directory: the reference to the directory to use
+ """
+ DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
+ self.index = UserIndex(self)
+ self.type = DirectoryTypePrincipalProvisioningResource.typeUser
+
+ def listNames(self):
+ """
+ List all the names currently in the directory.
+
+ @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
+ """
+
+ # Lookup all users
+ return [i[0] for i in opendirectory.listUsers(self.directory)]
+
+ def listIndexAttributes(self):
+ """
+ List all the names currently in the directory with specific attributes needed for indexing.
+
+ @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+ The C{tuple} elements are: uid, guid, last-modified.
+ """
+ return opendirectory.listUsers(self.directory)
+
+ def listCommonAttributes(self, names):
+ """
+ List specified names currently in the directory returning useful attributes.
+
+ @param names: C{list} of record entries to list attributes for.
+ @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+ or C{None} on failure.
+ """
+ return opendirectory.listUsersWithAttributes(self.directory, names)
+
+ def validName(self, name):
+ """
+ Verify that the supplied name exists as an entry in the directory.
+
+ @param name: C{str} of the namer to check.
+ @return: C{True} if the name exists, C{False} otherwise.
+ """
+ return opendirectory.checkUser(self.directory, name)
+
+ def directoryAttributes(self, name):
+ """
+ Return the attributes relevant to the directory entry.
+
+ @param name: C{str} containing the name for the directory entry.
+ @return: C{dict} containing the attribute key/value map.
+ """
+ result = opendirectory.listUsersWithAttributes(self.directory, [name])
+ if result:
+ return result[name]
+ else:
+ return None
+
+ def getTitle(self):
+ return "User Principals"
+
+class DirectoryGroupPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
+ """
+ L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+ as needed.
+ """
+ def __init__(self, path, url):
+ """
+ @param path: the path to the file which will back the resource.
+ @param url: the primary URL for the resource. Provisioned child
+ resources will use a URL based on C{url} as their primary URLs.
+ @param directory: the reference to the directory to use
+ """
+ DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
+ self.index = GroupIndex(self)
+ self.type = DirectoryTypePrincipalProvisioningResource.typeGroup
+
+ def listNames(self):
+ """
+ List all the names currently in the directory.
+
+ @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
+ """
+
+ # Lookup all users
+ return [i[0] for i in opendirectory.listGroups(self.directory)]
+
+ def listIndexAttributes(self):
+ """
+ List all the names currently in the directory with specific attributes needed for indexing.
+
+ @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+ The C{tuple} elements are: uid, guid, last-modified.
+ """
+ return opendirectory.listGroups(self.directory)
+
+ def listCommonAttributes(self, names):
+ """
+ List specified names currently in the directory returning useful attributes.
+
+ @param names: C{list} of record entries to list attributes for.
+ @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+ or C{None} on failure.
+ """
+ return opendirectory.listGroupsWithAttributes(self.directory, names)
+
+ def validName(self, name):
+ """
+ Verify that the supplied name exists as an entry in the directory.
+
+ @param name: C{str} of the namer to check
+ @return: C{True} if the name exists, C{False} otherwise
+ """
+ return opendirectory.checkGroup(self.directory, name)
+
+ def directoryAttributes(self, name):
+ """
+ Return the attributes relevant to the directory entry.
+
+ @param name: C{str} containing the name for the directory entry.
+ @return: C{dict} containing the attribute key/value map.
+ """
+ result = opendirectory.listGroupsWithAttributes(self.directory, [name])
+ if result:
+ return result[name]
+ else:
+ return None
+
+ def getTitle(self):
+ return "Group Principals"
+
+class DirectoryResourcePrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
+ """
+ L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+ as needed.
+ """
+ def __init__(self, path, url):
+ """
+ @param path: the path to the file which will back the resource.
+ @param url: the primary URL for the resource. Provisioned child
+ resources will use a URL based on C{url} as their primary URLs.
+ @param directory: the reference to the directory to use
+ """
+ DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
+ self.index = ResourceIndex(self)
+ self.type = DirectoryTypePrincipalProvisioningResource.typeResource
+
+ def listNames(self):
+ """
+ List all the names currently in the directory.
+
+ @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
+ """
+
+ # Lookup all users
+ return [i[0] for i in opendirectory.listResources(self.directory)]
+
+ def listIndexAttributes(self):
+ """
+ List all the names currently in the directory with specific attributes needed for indexing.
+
+ @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+ The C{tuple} elements are: uid, guid, last-modified.
+ """
+ return opendirectory.listResources(self.directory)
+
+ def listCommonAttributes(self, names):
+ """
+ List specified names currently in the directory returning useful attributes.
+
+ @param names: C{list} of record entries to list attributes for.
+ @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+ or C{None} on failure.
+ """
+ return opendirectory.listResourcesWithAttributes(self.directory, names)
+
+ def validName(self, name):
+ """
+ Verify that the supplied name exists as an entry in the directory.
+
+ @param name: C{str} of the namer to check
+ @return: C{True} if the name exists, C{False} otherwise
+ """
+ return opendirectory.checkResource(self.directory, name)
+
+ def directoryAttributes(self, name):
+ """
+ Return the attributes relevant to the directory entry.
+
+ @param name: C{str} containing the name for the directory entry.
+ @return: C{dict} containing the attribute key/value map.
+ """
+ result = opendirectory.listResourcesWithAttributes(self.directory, [name])
+ if result:
+ return result[name]
+ else:
+ return None
+
+ def getTitle(self):
+ return "Resource Principals"
+
+class DirectoryPrincipalProvisioningResource (DAVFile):
+ """
+ L{DAVFile} resource which provisions calendar principal resources as needed.
+ """
+ def __init__(self, path, url, params={}):
+ """
+ @param path: the path to the file which will back the resource.
+ @param url: the primary URL for the resource. Provisioned child
+ resources will use a URL based on C{url} as their primary URLs.
+ """
+ super(DirectoryPrincipalProvisioningResource, self).__init__(path)
+
+ assert self.exists(), "%s should exist" % (self,)
+ assert self.isCollection(), "%s should be a collection" % (self,)
+
+ # Extract parameters
+ if (params.has_key("DirectoryNode")):
+ self.directory = opendirectory.odInit(params["DirectoryNode"])
+ if self.directory is None:
+ raise ValueError("Failed to open Open Directory Node: %s" % (params["DirectoryNode"],))
+ else:
+ raise ValueError("DirectoryPrincipalProvisioningResource must be configured with an Open Directory Node")
+
+ # Create children
+ for name, clazz in (
+ ("users" , DirectoryUserPrincipalProvisioningResource),
+ ("groups" , DirectoryGroupPrincipalProvisioningResource),
+ ("resources" , DirectoryResourcePrincipalProvisioningResource),
+ ):
+ child_fp = self.fp.child(name)
+ if not child_fp.exists(): child_fp.makedirs()
+ principalCollection = clazz(child_fp.path, joinURL(url, name) + "/")
+ principalCollection.setup(self.directory)
+ self.putChild(name, principalCollection)
+
+ def isCollection(self):
+ """
+ See L{IDAVResource.isCollection}.
+ """
+ return True
+
+ def initialize(self, homeuri, home):
+ """
+ May be called during repository account initialization.
+ This implementation does nothing.
+
+ @param homeuri: C{str} uri of the calendar home root.
+ @param home: L{DAVFile} of the calendar home root.
+ """
+ for name in ("users", "groups", "resources",):
+ self.getChild(name).initialize(joinURL(homeuri, name), home.getChild(name))
+
+ def createSimilarFile(self, path):
+ return DAVFile(path)
+
+ def render(self, request):
+ return StatusResponse(
+ responsecode.OK,
+ "This collection contains principal resources",
+ title=self.displayName()
+ )
Deleted: CalendarServer/trunk/twistedcaldav/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory.py 2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/twistedcaldav/directory.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -1,1122 +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
-##
-
-"""
-Implements a directory-backed principal hierarchy.
-"""
-
-__all__ = [
- "DirectoryPrincipalFile",
- "DirectoryUserPrincipalProvisioningResource",
- "DirectoryGroupPrincipalProvisioningResource",
- "DirectoryResourcePrincipalProvisioningResource",
- "DirectoryPrincipalProvisioningResource",
-]
-
-from zope.interface import implements, Attribute, Interface
-
-from twisted.cred import checkers, credentials, error
-from twisted.cred.credentials import UsernamePassword
-from twisted.internet import reactor
-from twisted.internet import task
-from twisted.internet.defer import succeed
-from twisted.python import log
-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
-from twisted.web2.http import StatusResponse
-
-from twistedcaldav import caldavxml
-from twistedcaldav import customxml
-from twistedcaldav.principalindex import GroupIndex
-from twistedcaldav.principalindex import ResourceIndex
-from twistedcaldav.principalindex import UserIndex
-from twistedcaldav.resource import CalendarPrincipalCollectionResource
-from twistedcaldav.static import CalendarPrincipalFile
-
-import dsattributes
-import opendirectory
-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):
-
- # If there is no calendar principal URI then the calendar user is disabled.
- pcreds = IPrincipalCredentials(credentials)
- 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.authnPrincipal.directory(), user, pswd):
- return succeed((pcreds.authnURI, pcreds.authzURI,))
-
- raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
-
-class DirectoryPrincipalFile (CalendarPrincipalFile):
- """
- Directory principal resource.
- """
- def __init__(self, parent, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. This is the url which
- will be returned by L{principalURL}.
- """
- super(DirectoryPrincipalFile, self).__init__(path, url)
-
- self._parent = parent
-
- def checkCredentials(self, creds):
- """
- Check whether the provided credentials can be used to authenticate this prinicpal.
-
- @param creds: the L{ICredentials} for testing.
- @return: True if the credentials match, False otherwise
- """
-
- # If there is no calendar principal URI then the calendar user is disabled.
- if not self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
- return False
-
- if isinstance(creds, credentials.UsernamePassword):
- return opendirectory.authenticateUser(self._parent.directory, self.fp.basename(), creds.password)
- else:
- return False
-
- def directory(self):
- """
- Get the directory object used for directory operations.
-
- @return: C{object} for the directory instance
- """
-
- return self._parent.directory
-
- def groupMembers(self):
- """
- See L{IDAVPrincipalResource.groupMembers}.
- """
-
- # Check for the list of group member GUIDs
- if self.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
- # Get the list of GUIDs from the WebDAV private property
- memberguids = self.readDeadProperty(customxml.TwistedGroupMemberGUIDs())
- guids = [str(e) for e in memberguids.children]
-
- # Try to find each GUID in collections
- result = []
- for guid in guids:
- uri = DirectoryTypePrincipalProvisioningResource.findAnyGUID(guid)
- if uri is not None:
- result.append(uri)
-
- return result
-
- return ()
-
- def groupMemberships(self):
- """
- See L{IDAVPrincipalResource.groupMemberships}.
- """
-
- # Find any groups that match this user's GUID
- guid = self.getGUID()
- return DirectoryTypePrincipalProvisioningResource.findAnyGroupGUID(guid)
-
- def getPropertyValue(self, cls):
- """
- Get the requested proeprty value or return empty string.
- """
-
- if self.hasDeadProperty(cls()):
- prop = self.readDeadProperty(cls())
- return str(prop)
- else:
- return ""
-
- def setPropertyValue(self, str, cls):
- """
- Set the requested property value or remove it if the value is empty.
- """
-
- if str:
- self.writeDeadProperty(cls.fromString(str))
- else:
- self.removeDeadProperty(cls())
-
- def getGUID(self):
- return self.getPropertyValue(customxml.TwistedGUIDProperty)
-
- def readProperty(self, property, request):
- if type(property) is tuple:
- qname = property
- else:
- qname = property.qname()
-
- namespace, name = qname
-
- if namespace == caldavxml.caldav_namespace:
- if name == "calendar-user-address-set":
- 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):
- # This resource is read-only.
- raise HTTPError(StatusResponse(
- responsecode.FORBIDDEN,
- "Protected property %s may not be set." % (property.sname(),)
- ))
-
- def calendarUserAddresses(self):
- # Must have a valid calendar principal uri
- if self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
- return (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI),)
- else:
- # If there is no calendar principal URI then the calendar user is disabled so do not provide
- # a valid calendar address.
- return ()
-
- def matchesCalendarUserAddress(self, request, address):
- # By default we will always allow either a relative or absolute URI to the principal to
- # be supplied as a valid calendar user address.
-
- # Try calendar principal URI
- return self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI) and (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI) == address)
-
- def enable(self, calhome, enable):
- """
- Enable or disable this principal and access to any calendars owned by it.
-
- @param calhome: L{DAVFile} for the container of the calendar home of this user.
- @param enable: C{True} to enable, C{False} to disable.
- """
- # Get home collection resource
- calrsrc = calhome.getChild(self.principalUID())
-
- # Handle access for the calendar home
- if enable:
- calrsrc.disable(False)
- else:
- calrsrc.disable(True)
-
- def remove(self, calhome):
- """
- Remove this principal by hiding (archiving) any calendars owned by it. This is done
- by turning on the disabling and renaming the calendar home to ensure a future user
- with the same id won't see the old calendars.
-
- @param calhome: L{DAVFile} for the container of the calendar home of this user.
- """
-
- # Get home collection resource
- calrsrc = calhome.getChild(self.principalUID())
-
- # Disable access for the calendar home
- calrsrc.disable(True)
-
- # Rename the calendar home to the original name with the GUID appended
- newname = self.principalUID() + "-" + self.getPropertyValue(customxml.TwistedGUIDProperty)
-
- try:
- # Make sure the new name is not already in use
- if os.path.exists(newname):
- count = 1
- tempname = newname + "-%d"
- while(os.path.exists(tempname % count)):
- count += 1
- newname = tempname % count
- os.rename(calrsrc.fp.path, calrsrc.fp.sibling(newname).path)
- except OSError:
- log.msg("Directory: Failed to rename %s to %s when deleting a principal" %
- (calrsrc.fp.path, calrsrc.fp.sibling(newname).path))
-
- # Remove the disabled property to prevent lock out in the future
- calrsrc.disable(False)
-
-class DirectoryTypePrincipalProvisioningResource (CalendarPrincipalCollectionResource, DAVFile):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
-
- This includes a periodic task for refreshing the cached data.
- """
- periodicSyncIntervalSeconds = 60.0
-
- typeUnknown = 0
- typeUser = 1
- typeGroup = 2
- typeResource = 3
-
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- CalendarPrincipalCollectionResource.__init__(self, url)
- DAVFile.__init__(self, path)
- self.directory = None
- self.calendarhomeroot = None
- self.index = None
- self.type = DirectoryTypePrincipalProvisioningResource.typeUnknown
-
- def setup(self, directory):
- self.directory = directory
-
- def initialize(self, homeuri, home):
- """
- May be called during repository account initialization.
-
- @param homeuri: C{str} uri of the calendar home root.
- @param home: L{DAVFile} of the calendar home root.
- """
-
- # Make sure index is valid and sync with directory
- self.index.check()
- self.calendarhomeroot = (homeuri, home)
-
- #
- # There is a problem with the interaction of Directory Services and the
- # fork/fork process the server goes through to daemonize itself. For some
- # resaon, if DS is used before the fork, then calls to it afterwards all
- # return eServerSendError (-14740) errors.
- #
- # To get around this we must not use opendirectory module calls here, as this
- # method gets run before the fork/fork. So instead, we schedule a sync
- # operation to occur one second after the reactor starts up - which is after
- # the fork/fork. The problem with this is that the http server is already up
- # and running at that point BEFORE any initially provisioning is done.
- #
-
- # Create a periodic sync operation to keep the cached user list
- # in sync with the directory server.
- def periodicSync(self):
- self.syncNames()
-
- # Add initial sync operation
- reactor.callLater(1.0, periodicSync, self) #@UndefinedVariable
-
- # Add periodic sync operations
- runner = task.LoopingCall(periodicSync, self)
- runner.start(DirectoryTypePrincipalProvisioningResource.periodicSyncIntervalSeconds, now=False)
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containing C{str}'s for each name found, or C{None} if failed.
- """
- raise NotImplementedError
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- raise NotImplementedError
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- raise NotImplementedError
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory and that the
- name corresponds to one that can use the calendar.
-
- @param name: C{str} of the name to check.
- @return: C{True} if the name exists, C{False} otherwise.
- """
- raise NotImplementedError
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- raise NotImplementedError
-
- def syncNames(self):
- """
- Synchronize the data in the directory with the local cache of resources in the file system.
- """
- #log.msg("Directory: Synchronizing cache for %s" % (self.getTitle(),))
-
- # Get index entries from directory and from cache
- remoteindex = self.listIndexAttributes()
- localindex = self.index.listIndex()
-
- # Create dicts indexed by GUID for each
- remotedict = {}
- for entry in remoteindex:
- remotedict[entry[dsattributes.indexGUID]] = entry
- localdict = {}
- for entry in localindex:
- localdict[entry[dsattributes.indexGUID]] = entry
-
- # Extract list of GUIDs in each
- remoteguids = [entry[dsattributes.indexGUID] for entry in remoteindex]
- localguids = [entry[dsattributes.indexGUID] for entry in localindex]
-
- remoteguidset = set(remoteguids)
- remoteguids = None
- localguidset = set(localguids)
- localguids = None
-
- new_remote = list(remoteguidset.difference(localguidset))
- removed_remote = list(localguidset.difference(remoteguidset))
-
- # Remove old principals
- old_names = [localdict[guid][dsattributes.indexUID] for guid in removed_remote]
- old_names.sort()
- for name in old_names:
- self.removePrincipal(name, True)
-
- # Get new ones (but only those with a CalendarPrincipalURI attribute)
- new_names = [remotedict[guid][dsattributes.indexUID] for guid in new_remote if remotedict[guid][dsattributes.indexCalendarPrincipalURI]]
-
- # Get all the directory entries for the new ones in one go for better performance
- if new_names:
- new_entries = self.listCommonAttributes(new_names)
- new_names = [n for n in new_entries.iterkeys()]
- new_names.sort()
- for name in new_names:
- self.addPrincipal(name, attrs=new_entries[name], fast=True)
-
- # Look for changes in entries
- common_entries = list(remoteguidset.intersection(localguidset))
- for guid in common_entries:
- old = localdict[guid]
- new = remotedict[guid]
- if old != new:
- # Special case issue with unicode normalization of names
- if ((old[dsattributes.indexUID] != new [dsattributes.indexUID]) and
- (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
- unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8"))) and
- (old[1:] == new[1:])):
- continue
-
- self.changedPrincipal(old, new)
-
- # Commit index after all changes are done
- self.index.commit()
-
- def addPrincipal(self, name, attrs=None, fast=False):
- """
- Add a new principal resource to the server.
-
- @param name: C{str} containing the name of the resource to add.
- @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- """
- # This will create it
- child_fp = self.fp.child(name)
- assert not child_fp.exists()
-
- assert self.exists()
- assert self.isCollection()
-
- child_fp.open("w").close()
-
- # Now update the principal's cached data
- self.updatePrincipal(name, attrs=attrs, fast=fast, new=True, nolog=True)
-
- log.msg("Directory: Add %s to %s" % (name, self.getTitle()))
-
- def changedPrincipal(self, old, new, fast=False):
- """
- Change a new principal resource to sync with directory.
-
- @param old: C{str} containing the name of the original resource.
- @param new: C{str} containing the name of the new resource.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- """
- # Look for change to calendar enabled state
-
- # See if the name changed because that is a real pain!
- if ((old[dsattributes.indexUID] != new[dsattributes.indexUID]) and
- (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
- unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8")))):
- self.renamePrincipal(old[dsattributes.indexUID], new[dsattributes.indexUID])
-
- # See if change in enable state
- enable_state = old[dsattributes.indexCalendarPrincipalURI] != new[dsattributes.indexCalendarPrincipalURI]
-
- # Do update (no log message if enable state is being changed as that will generate a log message itself)
- self.updatePrincipal(new[dsattributes.indexUID], nolog=enable_state)
-
- # Look for change in calendar enable state
- if enable_state:
- self.enablePrincipal(new[dsattributes.indexUID], len(new[dsattributes.indexCalendarPrincipalURI]) != 0)
-
- def renamePrincipal(self, old, new):
- """
- Change a principal resource name to sync with directory.
-
- @param old: C{str} containing the name of the original resource.
- @param new: C{str} containing the name of the new resource.
- """
- log.msg("Directory: Renamed Principal %s to %s in %s" % (old, new, self.getTitle()))
- raise NotImplementedError
-
- def updatePrincipal(self, name, attrs = None, fast=False, new=False, nolog=False):
- """
- Update details about the named principal in the principal's own property store
- and the principal collection index.
-
- @param name: C{str} containing the principal name to update.
- @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- @param new: C{True} when this update is the result of adding a new principal,
- C{False} otherwise.
- """
- # Get attributes from directory
- if attrs is None:
- attrs = self.directoryAttributes(name)
- realname = attrs.get(dsattributes.attrRealName, None)
- guid = attrs.get(dsattributes.attrGUID, None)
- lastModified = attrs.get(dsattributes.attrLastModified, None)
- principalurl = attrs.get(dsattributes.attrCalendarPrincipalURI, None)
-
- # Do provisioning (if the principal is a resource we will turn on the auto-provisioning option)
- principal = self.getChild(name)
- if principal is None:
- log.msg("Directory: Failed to update missing principal: %s in %s" % (name, self.getTitle()))
- return
-
- if (new):
- principal.provisionCalendarAccount(name, None, True, None, self.calendarhomeroot,
- None, ["calendar"], self.type == DirectoryTypePrincipalProvisioningResource.typeResource)
-
- # Add directory specific attributes to principal
- principal.setPropertyValue(realname, davxml.DisplayName)
- principal.setPropertyValue(guid, customxml.TwistedGUIDProperty)
- principal.setPropertyValue(lastModified, customxml.TwistedLastModifiedProperty)
- principal.setPropertyValue(principalurl, customxml.TwistedCalendarPrincipalURI)
-
- # Special for group
- if self.type == DirectoryTypePrincipalProvisioningResource.typeGroup:
- # Get comma separated list of members and split into a list
- groupmembers = attrs.get(dsattributes.attrGroupMembers, None)
- if isinstance(groupmembers, list):
- members = groupmembers
- elif isinstance(groupmembers, str):
- members = [groupmembers]
- else:
- members = []
-
- # Create and write the group property
- children = [customxml.TwistedGUIDProperty.fromString(s) for s in members]
- principal.writeDeadProperty(customxml.TwistedGroupMemberGUIDs(*children))
-
- # Do index
- self.index.addPrincipal(name, principal, fast)
-
- if not nolog:
- log.msg("Directory: Updated %s in %s" % (name, self.getTitle()))
-
- def enablePrincipal(self, name, enable):
- """
- Enable or disable calendar access for this principal.
-
- @param enable: C{True} to enable, C{False} to disable
- """
- principal = self.getChild(name)
- if principal is None:
- log.msg("Directory: Failed to enable/disable missing principal: %s in %s" % (name, self.getTitle()))
- return
-
- if enable:
- principal.enable(self.calendarhomeroot[1], True)
- log.msg("Directory: Enabled %s in %s" % (name, self.getTitle()))
- else:
- principal.enable(self.calendarhomeroot[1], False)
- log.msg("Directory: Disabled %s in %s" % (name, self.getTitle()))
-
- def removePrincipal(self, name, fast=False):
- """
- Remove a principal from the cached resources.
-
- @param name: C{str} containing the name of the principal to remove.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- """
-
- # Get the principal to 'hide' its calendars
- principal = self.getChild(name)
- if principal is None:
- log.msg("Directory: Failed to remove missing principal: %s in %s" % (name, self.getTitle()))
- else:
- principal.remove(self.calendarhomeroot[1])
-
- # Now remove the principal resource itself
- child_fp = self.fp.child(name)
- if child_fp.exists():
- os.remove(child_fp.path)
-
- # Do index
- self.index.deleteName(name, fast)
-
- log.msg("Directory: Delete %s from %s" % (name, self.getTitle()))
-
- @classmethod
- def findAnyGUID(clazz, guid):
- """
- Find the principal associated with the specified GUID.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{str} with matching principal URI, or C{None}
- """
- for url in clazz._principleCollectionSet.keys():
- try:
- pcollection = clazz._principleCollectionSet[url]
- if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
- principal = pcollection.findGUID(guid)
- if principal is not None:
- return principal
- except ReferenceError:
- pass
-
- return None
-
- def findGUID(self, guid):
- """
- See if a principal with the specified GUID exists and if so return its principal URI.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{str} with matching principal URI, or C{None}
- """
- name = self.index.nameFromGUID(guid)
- if name is not None:
- principal = self.getChild(name)
- return principal.principalURL()
- else:
- return None
-
- @classmethod
- def findAnyGroupGUID(clazz, guid):
- """
- Find the principals containing the specified GUID as a group member.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{list} with matching principal URIs
- """
-
- result = []
- for url in clazz._principleCollectionSet.keys():
- try:
- pcollection = clazz._principleCollectionSet[url]
- if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
- result.extend(pcollection.findGroupGUID(guid))
- except ReferenceError:
- pass
-
- return result
-
- def findGroupGUID(self, guid):
- """
- Find principals with the specified GUID as a group member.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{list} with matching principal URIs
- """
- # Only both for group collections
- if self.type != DirectoryTypePrincipalProvisioningResource.typeGroup:
- return []
-
- result = []
- for name in self.listChildren():
- principal = self.getChild(name)
- if principal.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
- guids = principal.readDeadProperty(customxml.TwistedGroupMemberGUIDs)
- for g in guids.children:
- if str(g) == guid:
- result.append(principal.principalURL())
- break
-
- return result
-
- def isCollection(self):
- """
- See L{IDAVResource.isCollection}.
- """
- return True
-
- def getChild(self, name):
- """
- Look up a child resource.
- @return: the child of this resource with the given name.
- """
- if name == "":
- return self
-
- child = self.putChildren.get(name, None)
- if child: return child
-
- child_fp = self.fp.child(name)
- if child_fp.exists():
- return DirectoryPrincipalFile(self, child_fp.path, joinURL(self._url, name))
- else:
- return None
-
- def principalSearchPropertySet(self):
- """
- See L{IDAVResource.principalSearchPropertySet}.
- """
- return davxml.PrincipalSearchPropertySet(
- davxml.PrincipalSearchProperty(
- davxml.PropertyContainer(
- davxml.DisplayName()
- ),
- davxml.Description(
- davxml.PCDATAElement("Display Name"),
- **{"xml:lang":"en"}
- ),
- ),
- davxml.PrincipalSearchProperty(
- davxml.PropertyContainer(
- caldavxml.CalendarUserAddressSet()
- ),
- davxml.Description(
- davxml.PCDATAElement("Calendar User Addresses"),
- **{"xml:lang":"en"}
- ),
- ),
- )
-
- def createSimilarFile(self, path):
- if path == self.fp.path:
- return self
- else:
- # TODO: Fix this - not sure how to get URI for second argument of __init__
- return CalendarPrincipalFile(path, "")
-
- def http_PUT (self, request): return responsecode.FORBIDDEN
- def http_MKCOL (self, request): return responsecode.FORBIDDEN
- def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
-
-class DirectoryUserPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
- """
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
- self.index = UserIndex(self)
- self.type = DirectoryTypePrincipalProvisioningResource.typeUser
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
- """
-
- # Lookup all users
- return [i[0] for i in opendirectory.listUsers(self.directory)]
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- return opendirectory.listUsers(self.directory)
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- return opendirectory.listUsersWithAttributes(self.directory, names)
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory.
-
- @param name: C{str} of the namer to check.
- @return: C{True} if the name exists, C{False} otherwise.
- """
- return opendirectory.checkUser(self.directory, name)
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- result = opendirectory.listUsersWithAttributes(self.directory, [name])
- if result:
- return result[name]
- else:
- return None
-
- def getTitle(self):
- return "User Principals"
-
-class DirectoryGroupPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
- """
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
- self.index = GroupIndex(self)
- self.type = DirectoryTypePrincipalProvisioningResource.typeGroup
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
- """
-
- # Lookup all users
- return [i[0] for i in opendirectory.listGroups(self.directory)]
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- return opendirectory.listGroups(self.directory)
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- return opendirectory.listGroupsWithAttributes(self.directory, names)
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory.
-
- @param name: C{str} of the namer to check
- @return: C{True} if the name exists, C{False} otherwise
- """
- return opendirectory.checkGroup(self.directory, name)
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- result = opendirectory.listGroupsWithAttributes(self.directory, [name])
- if result:
- return result[name]
- else:
- return None
-
- def getTitle(self):
- return "Group Principals"
-
-class DirectoryResourcePrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
- """
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
- self.index = ResourceIndex(self)
- self.type = DirectoryTypePrincipalProvisioningResource.typeResource
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
- """
-
- # Lookup all users
- return [i[0] for i in opendirectory.listResources(self.directory)]
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- return opendirectory.listResources(self.directory)
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- return opendirectory.listResourcesWithAttributes(self.directory, names)
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory.
-
- @param name: C{str} of the namer to check
- @return: C{True} if the name exists, C{False} otherwise
- """
- return opendirectory.checkResource(self.directory, name)
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- result = opendirectory.listResourcesWithAttributes(self.directory, [name])
- if result:
- return result[name]
- else:
- return None
-
- def getTitle(self):
- return "Resource Principals"
-
-class DirectoryPrincipalProvisioningResource (DAVFile):
- """
- L{DAVFile} resource which provisions calendar principal resources as needed.
- """
- def __init__(self, path, url, params={}):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- """
- super(DirectoryPrincipalProvisioningResource, self).__init__(path)
-
- assert self.exists(), "%s should exist" % (self,)
- assert self.isCollection(), "%s should be a collection" % (self,)
-
- # Extract parameters
- if (params.has_key("DirectoryNode")):
- self.directory = opendirectory.odInit(params["DirectoryNode"])
- if self.directory is None:
- raise ValueError("Failed to open Open Directory Node: %s" % (params["DirectoryNode"],))
- else:
- raise ValueError("DirectoryPrincipalProvisioningResource must be configured with an Open Directory Node")
-
- # Create children
- for name, clazz in (
- ("users" , DirectoryUserPrincipalProvisioningResource),
- ("groups" , DirectoryGroupPrincipalProvisioningResource),
- ("resources" , DirectoryResourcePrincipalProvisioningResource),
- ):
- child_fp = self.fp.child(name)
- if not child_fp.exists(): child_fp.makedirs()
- principalCollection = clazz(child_fp.path, joinURL(url, name) + "/")
- principalCollection.setup(self.directory)
- self.putChild(name, principalCollection)
-
- def isCollection(self):
- """
- See L{IDAVResource.isCollection}.
- """
- return True
-
- def initialize(self, homeuri, home):
- """
- May be called during repository account initialization.
- This implementation does nothing.
-
- @param homeuri: C{str} uri of the calendar home root.
- @param home: L{DAVFile} of the calendar home root.
- """
- for name in ("users", "groups", "resources",):
- self.getChild(name).initialize(joinURL(homeuri, name), home.getChild(name))
-
- def createSimilarFile(self, path):
- return DAVFile(path)
-
- def render(self, request):
- return StatusResponse(
- responsecode.OK,
- "This collection contains principal resources",
- title=self.displayName()
- )
Modified: CalendarServer/trunk/twistedcaldav/repository.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/repository.py 2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/twistedcaldav/repository.py 2006-11-03 01:40:54 UTC (rev 358)
@@ -42,10 +42,11 @@
from twisted.web2.server import Site
from twistedcaldav import caldavxml, customxml
+from twistedcaldav import authkerb
from twistedcaldav.logging import RotatingFileAccessLoggingObserver
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.static import CalDAVFile, CalendarHomeFile, CalendarPrincipalFile
-from twistedcaldav import authkerb, directory
+from twistedcaldav.directory.cred import DirectoryCredentialsChecker
import os
@@ -191,7 +192,7 @@
portal.registerChecker(auth.TwistedPropertyChecker())
print "Using property-based password checker."
elif authenticator.credentials == ATTRIBUTE_VALUE_DIRECTORY:
- portal.registerChecker(directory.DirectoryCredentialsChecker())
+ portal.registerChecker(DirectoryCredentialsChecker())
print "Using directory-based password checker."
elif authenticator.credentials == ATTRIBUTE_VALUE_KERBEROS:
if authenticator.type == "basic":
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061102/0fd56568/attachment.html
More information about the calendarserver-changes
mailing list