[CalendarServer-changes] [488]
CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/
directory
source_changes at macosforge.org
source_changes at macosforge.org
Thu Nov 16 12:02:07 PST 2006
Revision: 488
http://trac.macosforge.org/projects/calendarserver/changeset/488
Author: cdaboo at apple.com
Date: 2006-11-16 12:02:06 -0800 (Thu, 16 Nov 2006)
Log Message:
-----------
XML file based directory service.
Added Paths:
-----------
CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.dtd
CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.xml
CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_xmlfile.py
CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/xmlfile.py
Added: CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.dtd
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.dtd (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.dtd 2006-11-16 20:02:06 UTC (rev 488)
@@ -0,0 +1,39 @@
+<!--
+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
+ -->
+
+<!ELEMENT accounts (user*, group*, resource*) >
+
+ <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+ <!ATTLIST user repeat CDATA "1">
+
+ <!ELEMENT group (uid, pswd, name, members, cuaddr*, calendar*, quota?)>
+ <!ATTLIST group repeat CDATA "1">
+
+ <!ELEMENT resource (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+ <!ATTLIST resource repeat CDATA "1">
+
+ <!ELEMENT uid (#PCDATA)>
+ <!ELEMENT pswd (#PCDATA)>
+ <!ELEMENT name (#PCDATA)>
+ <!ELEMENT cuaddr (#PCDATA)>
+ <!ELEMENT calendar (#PCDATA)>
+ <!ELEMENT quota (#PCDATA)>
+ <!ELEMENT autorespond EMPTY>
+ <!ELEMENT canproxy EMPTY>
+ <!ELEMENT members (uid*)>
+
\ No newline at end of file
Added: CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.xml (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.xml 2006-11-16 20:02:06 UTC (rev 488)
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts>
+ <user>
+ <uid>admin</uid>
+ <pswd>admin</pswd>
+ <name>Super User</name>
+ </user>
+ <user>
+ <uid>proxy</uid>
+ <pswd>proxy</pswd>
+ <name>User who can authorize as someone else</name>
+ <canproxy/>
+ </user>
+ <user>
+ <uid>wsanchez</uid>
+ <pswd>wsanchez</pswd>
+ <name>Wilfredo Sanchez</name>
+ <cuaddr>mailto:wsanchez at example.com</cuaddr>
+ <calendar>calendar</calendar>
+ </user>
+ <user>
+ <uid>cdaboo</uid>
+ <pswd>cdaboo</pswd>
+ <name>Cyrus Daboo</name>
+ <cuaddr>mailto:cdaboo at example.com</cuaddr>
+ <calendar>calendar</calendar>
+ </user>
+ <user>
+ <uid>lecroy</uid>
+ <pswd>lecroy</pswd>
+ <name>Chris Lecroy</name>
+ <cuaddr>mailto:lecroy at example.com</cuaddr>
+ <calendar>calendar</calendar>
+ </user>
+ <user>
+ <uid>dreid</uid>
+ <pswd>dreid</pswd>
+ <name>David Reid</name>
+ <cuaddr>mailto:dreid at example.com</cuaddr>
+ <calendar>calendar</calendar>
+ </user>
+ <user repeat='2'>
+ <uid>user%02d</uid>
+ <pswd>user%02d</pswd>
+ <name>User %02d</name>
+ <calendar>calendar</calendar>
+ </user>
+ <group>
+ <uid>managers</uid>
+ <pswd>managers</pswd>
+ <name>Managers</name>
+ <members>
+ <uid>lecroy</uid>
+ </members>
+ </group>
+ <group>
+ <uid>grunts</uid>
+ <pswd>grunts</pswd>
+ <name>We do all the work</name>
+ <members>
+ <uid>wsanchez</uid>
+ <uid>cdaboo</uid>
+ <uid>dreid</uid>
+ </members>
+ </group>
+ <group>
+ <uid>right_coast</uid>
+ <pswd>right_coast</pswd>
+ <name>East Coast</name>
+ <members>
+ <uid>cdaboo</uid>
+ </members>
+ </group>
+ <group>
+ <uid>left_coast</uid>
+ <pswd>left_coast</pswd>
+ <name>West Coast</name>
+ <members>
+ <uid>wsanchez</uid>
+ <uid>lecroy</uid>
+ <uid>dreid</uid>
+ </members>
+ </group>
+ <resource>
+ <uid>mercury</uid>
+ <pswd>mercury</pswd>
+ <name>Mecury Seven</name>
+ <cuaddr>mailto:mercury at example.com</cuaddr>
+ <calendar>calendar</calendar>
+ </resource>
+ <resource>
+ <uid>gemini</uid>
+ <pswd>gemini</pswd>
+ <name>Gemini Twelve</name>
+ <cuaddr>mailto:gemini at example.com</cuaddr>
+ <calendar>calendar</calendar>
+ </resource>
+ <resource>
+ <uid>apollo</uid>
+ <pswd>apollo</pswd>
+ <name>Apollo Eleven</name>
+ <cuaddr>mailto:apollo at example.com</cuaddr>
+ <calendar>calendar</calendar>
+ </resource>
+</accounts>
Added: CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_xmlfile.py (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_xmlfile.py 2006-11-16 20:02:06 UTC (rev 488)
@@ -0,0 +1,138 @@
+##
+# Copyright (c) 2005-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
+##
+
+import os
+
+import twisted.trial.unittest
+from twisted.cred.credentials import UsernamePassword
+
+from twistedcaldav.directory.xmlfile import XMLFileService
+
+users = set(
+ ("admin" ,
+ "proxy" ,
+ "wsanchez",
+ "cdaboo" ,
+ "lecroy" ,
+ "dreid" ,
+ "user01" ,
+ "user02" ,)
+)
+
+groups = {
+ "managers" : ("lecroy",),
+ "grunts" : ("wsanchez", "cdaboo", "dreid"),
+ "right_coast": ("cdaboo",),
+ "left_coast" : ("wsanchez", "dreid", "lecroy"),
+}
+
+resources = set(
+ ("mercury",
+ "gemini" ,
+ "apollo" ,)
+)
+
+xmlFile = os.path.join(os.path.dirname(__file__), "accounts.xml")
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class Basic (twisted.trial.unittest.TestCase):
+ """
+ Test XML file based directory implementation.
+ """
+ def test_recordTypes(self):
+ """
+ XMLFileService.recordTypes(xmlFile)
+ """
+ service = XMLFileService(xmlFile)
+ self.assertEquals(set(service.recordTypes()), set(("user", "group", "resource")))
+
+ def test_listRecords_user(self):
+ """
+ XMLFileService.listRecords("user")
+ """
+ service = XMLFileService(xmlFile)
+ self.assertEquals(set(service.listRecords("user")), users)
+
+ def test_listRecords_group(self):
+ """
+ XMLFileService.listRecords("group")
+ """
+ service = XMLFileService(xmlFile)
+ self.assertEquals(set(service.listRecords("group")), set(groups.keys()))
+
+ def test_listRecords_resources(self):
+ """
+ XMLFileService.listRecords("resource")
+ """
+ service = XMLFileService(xmlFile)
+ self.assertEquals(set(service.listRecords("resource")), resources)
+
+ def test_recordWithShortName_user(self):
+ """
+ XMLFileService.recordWithShortName("user")
+ """
+ service = XMLFileService(xmlFile)
+ for user in users:
+ record = service.recordWithShortName("user", user)
+ self.assertEquals(record.shortName, user)
+
+ def test_recordWithShortName_group(self):
+ """
+ XMLFileService.recordWithShortName("group")
+ """
+ service = XMLFileService(xmlFile)
+ for group in groups:
+ groupRecord = service.recordWithShortName("group", group)
+ self.assertEquals(groupRecord.shortName, group)
+
+ def test_recordWithShortName_resource(self):
+ """
+ XMLFileService.recordWithShortName("resource")
+ """
+ service = XMLFileService(xmlFile)
+ for resource in resources:
+ resourceRecord = service.recordWithShortName("resource", resource)
+ self.assertEquals(resourceRecord.shortName, resource)
+
+ def test_groupMembers(self):
+ """
+ FileDirectoryRecord.members()
+ """
+ service = XMLFileService(xmlFile)
+ for group in groups:
+ groupRecord = service.recordWithShortName("group", group)
+ self.assertEquals(set(m.shortName for m in groupRecord.members()), set(groups[group]))
+
+ def test_groupMemberships(self):
+ """
+ FileDirectoryRecord.groups()
+ """
+ service = XMLFileService(xmlFile)
+ for user in users:
+ userRecord = service.recordWithShortName("user", user)
+ self.assertEquals(set(g.shortName for g in userRecord.groups()), set(g for g in groups if user in groups[g]))
+
+ def test_verifyCredentials(self):
+ """
+ FileDirectoryRecord.verifyCredentials()
+ """
+ service = XMLFileService(xmlFile)
+ for user in users:
+ userRecord = service.recordWithShortName("user", user)
+ self.failUnless(userRecord.verifyCredentials(UsernamePassword(user, user)))
Added: CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/xmlfile.py (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/xmlfile.py 2006-11-16 20:02:06 UTC (rev 488)
@@ -0,0 +1,266 @@
+##
+# 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
+##
+
+
+"""
+XML based user/group/resource directory service implementation.
+"""
+
+__all__ = [
+ "XMLFileService",
+ "XMLFileRecord",
+]
+
+import xml.dom.minidom
+
+from twisted.cred.credentials import UsernamePassword
+from twisted.python.filepath import FilePath
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.resource import CalDAVResource
+
+ELEMENT_ACCOUNTS = "accounts"
+ELEMENT_USER = "user"
+ELEMENT_GROUP = "group"
+ELEMENT_RESOURCE = "resource"
+
+ELEMENT_USERID = "uid"
+ELEMENT_PASSWORD = "pswd"
+ELEMENT_NAME = "name"
+ELEMENT_MEMBERS = "members"
+ELEMENT_CUADDR = "cuaddr"
+ELEMENT_CALENDAR = "calendar"
+ELEMENT_QUOTA = "quota"
+ELEMENT_AUTORESPOND = "autorespond"
+ELEMENT_CANPROXY = "canproxy"
+
+ATTRIBUTE_REPEAT = "repeat"
+
+class XMLFileService(DirectoryService):
+ """
+ XML based implementation of L{IDirectoryService}.
+ """
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+ def __init__(self, xmlFile):
+ if type(xmlFile) is str:
+ xmlFile = FilePath(xmlFile)
+
+ self.xmlFile = xmlFile
+ self.items = {}
+
+ def recordTypes(self):
+ recordTypes = ("user", "group", "resource")
+ return recordTypes
+
+ def listRecords(self, recordType):
+ for entryShortName, xmlprincipal in self._entriesForRecordType(recordType):
+ yield entryShortName
+
+ def recordWithShortName(self, recordType, shortName):
+ for entryShortName, xmlprincipal in self._entriesForRecordType(recordType):
+ if entryShortName == shortName:
+ return XMLFileRecord(
+ service = self,
+ recordType = recordType,
+ shortName = entryShortName,
+ xmlPrincipal = xmlprincipal,
+ )
+
+ raise NotImplementedError()
+
+ def recordWithGUID(self, guid):
+ raise NotImplementedError()
+
+ def _entriesForRecordType(self, recordType):
+ # Read in XML
+ fd = open(self.xmlFile.path, "r")
+ doc = xml.dom.minidom.parse( fd )
+ fd.close()
+
+ # Verify that top-level element is correct
+ accounts_node = doc._get_documentElement()
+ if accounts_node._get_localName() != ELEMENT_ACCOUNTS:
+ self.log("Ignoring file %r because it is not a repository builder file" % (self.xmlFile,))
+ return
+ self._parseXML(accounts_node)
+
+ for entry in self.items.itervalues():
+ if entry.recordType == recordType:
+ yield entry.uid, entry
+
+ self.items = {}
+
+ def _parseXML(self, node):
+ """
+ Parse the XML root node from the accounts configuration document.
+ @param node: the L{Node} to parse.
+ """
+ self.items = {}
+ for child in node._get_childNodes():
+ if child._get_localName() in (ELEMENT_USER, ELEMENT_GROUP, ELEMENT_RESOURCE):
+ if child.hasAttribute( ATTRIBUTE_REPEAT ):
+ repeat = int(child.getAttribute( ATTRIBUTE_REPEAT ))
+ else:
+ repeat = 1
+
+ recordType = {
+ ELEMENT_USER: "user",
+ ELEMENT_GROUP: "group",
+ ELEMENT_RESOURCE:"resource",}[child._get_localName()]
+
+ principal = XMLPrincipal(recordType)
+ principal.parseXML( child )
+ if repeat > 1:
+ for ctr in range(repeat):
+ newprincipal = principal.repeat(ctr + 1)
+ self.items[newprincipal.uid] = newprincipal
+ if recordType == "group":
+ self._updateMembership(newprincipal)
+ else:
+ self.items[principal.uid] = principal
+ if recordType == "group":
+ self._updateMembership(principal)
+
+ def _updateMembership(self, group):
+ # Update group membership
+ for member in group.members:
+ if self.items.has_key(member):
+ self.items[member].groups.append(group.uid)
+
+class XMLPrincipal (object):
+ """
+ Contains provision information for one user.
+ """
+ def __init__(self, recordType):
+ """
+ @param recordType: record type for directory entry.
+ """
+
+ self.recordType = recordType
+ self.uid = None
+ self.pswd = None
+ self.name = None
+ self.members = []
+ self.groups = []
+ self.cuaddrs = []
+ self.calendars = []
+ self.quota = None
+ self.autorespond = None
+
+ def repeat(self, ctr):
+ """
+ Create another object like this but with all text items having % substitution
+ done on them with the numeric value provided.
+ @param ctr: an integer to substitute into text.
+ """
+
+ if self.uid.find("%") != -1:
+ uid = self.uid % ctr
+ else:
+ uid = self.uid
+ if self.pswd.find("%") != -1:
+ pswd = self.pswd % ctr
+ else:
+ pswd = self.pswd
+ if self.name.find("%") != -1:
+ name = self.name % ctr
+ else:
+ name = self.name
+ cuaddrs = []
+ for cuaddr in self.cuaddrs:
+ if cuaddr.find("%") != -1:
+ cuaddrs.append(cuaddr % ctr)
+ else:
+ cuaddrs.append(cuaddr)
+
+ result = XMLPrincipal(self.recordType)
+ result.uid = uid
+ result.pswd = pswd
+ result.name = name
+ result.members = self.members
+ result.cuaddrs = cuaddrs
+ result.calendars = self.calendars
+ result.quota = self.quota
+ result.autorespond = self.autorespond
+ return result
+
+ def parseXML( self, node ):
+
+ for child in node._get_childNodes():
+ if child._get_localName() == ELEMENT_USERID:
+ if child.firstChild is not None:
+ self.uid = child.firstChild.data.encode("utf-8")
+ elif child._get_localName() == ELEMENT_PASSWORD:
+ if child.firstChild is not None:
+ self.pswd = child.firstChild.data.encode("utf-8")
+ elif child._get_localName() == ELEMENT_NAME:
+ if child.firstChild is not None:
+ self.name = child.firstChild.data.encode("utf-8")
+ elif child._get_localName() == ELEMENT_MEMBERS:
+ self._parseMembers(child)
+ elif child._get_localName() == ELEMENT_CUADDR:
+ if child.firstChild is not None:
+ self.cuaddrs.append(child.firstChild.data.encode("utf-8"))
+ elif child._get_localName() == ELEMENT_CALENDAR:
+ if child.firstChild is not None:
+ self.calendars.append(child.firstChild.data.encode("utf-8"))
+ elif child._get_localName() == ELEMENT_QUOTA:
+ if child.firstChild is not None:
+ self.quota = int(child.firstChild.data.encode("utf-8"))
+ elif child._get_localName() == ELEMENT_AUTORESPOND:
+ self.autorespond = True
+ elif child._get_localName() == ELEMENT_CANPROXY:
+ CalDAVResource.proxyUsers.add(self.uid)
+
+ def _parseMembers( self, node ):
+
+ for child in node._get_childNodes():
+ if child._get_localName() == ELEMENT_USERID:
+ if child.firstChild is not None:
+ self.members.append(child.firstChild.data.encode("utf-8"))
+
+class XMLFileRecord(DirectoryRecord):
+ """
+ XML based implementation implementation of L{IDirectoryRecord}.
+ """
+ def __init__(self, service, recordType, shortName, xmlPrincipal):
+
+ self.service = service
+ self.recordType = recordType
+ self.guid = None
+ self.shortName = shortName
+ self.fullName = xmlPrincipal.name
+ self.clearPassword = xmlPrincipal.pswd
+ self._members = xmlPrincipal.members
+ self._groups = xmlPrincipal.groups
+
+ def members(self):
+ for shortName in self._members:
+ yield self.service.recordWithShortName("user", shortName)
+
+ def groups(self):
+ for shortName in self._groups:
+ yield self.service.recordWithShortName("group", shortName)
+
+ def verifyCredentials(self, credentials):
+ if isinstance(credentials, UsernamePassword):
+ return credentials.password == self.clearPassword
+
+ return super(XMLFileRecord, self).verifyCredentials(credentials)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061116/a6f5ef47/attachment.html
More information about the calendarserver-changes
mailing list