[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