[CalendarServer-changes] [656] CalendarServer/branches/SACLs

source_changes at macosforge.org source_changes at macosforge.org
Mon Dec 4 16:22:03 PST 2006


Revision: 656
          http://trac.macosforge.org/projects/calendarserver/changeset/656
Author:   dreid at apple.com
Date:     2006-12-04 16:22:02 -0800 (Mon, 04 Dec 2006)

Log Message:
-----------
SACL support using PyAppleAuth

Modified Paths:
--------------
    CalendarServer/branches/SACLs/conf/caldavd.plist
    CalendarServer/branches/SACLs/conf/repository.xml
    CalendarServer/branches/SACLs/twistedcaldav/caldavd.py
    CalendarServer/branches/SACLs/twistedcaldav/repository.py

Added Paths:
-----------
    CalendarServer/branches/SACLs/twistedcaldav/root.py
    CalendarServer/branches/SACLs/twistedcaldav/test/test_root.py

Modified: CalendarServer/branches/SACLs/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/SACLs/conf/caldavd.plist	2006-12-05 00:05:57 UTC (rev 655)
+++ CalendarServer/branches/SACLs/conf/caldavd.plist	2006-12-05 00:22:02 UTC (rev 656)
@@ -120,5 +120,11 @@
   <key>twistdLocation</key>
   <string>/usr/share/caldavd/bin/twistd</string>
 
+  <key>SACLEnable</key>
+  <false/>
+  
+  <key>SACLService</key>
+  <string>com.apple.access_calendar</string>
+
 </dict>
 </plist>

Modified: CalendarServer/branches/SACLs/conf/repository.xml
===================================================================
--- CalendarServer/branches/SACLs/conf/repository.xml	2006-12-05 00:05:57 UTC (rev 655)
+++ CalendarServer/branches/SACLs/conf/repository.xml	2006-12-05 00:22:02 UTC (rev 656)
@@ -22,7 +22,7 @@
 
   <docroot auto-principal-collection-set="yes">
     <collection>
-      <pytype>twisted.web2.dav.static.DAVFile</pytype>
+      <pytype>twistedcaldav.root.RootResource</pytype>
       <properties>
         <acl>
           <ace>

Modified: CalendarServer/branches/SACLs/twistedcaldav/caldavd.py
===================================================================
--- CalendarServer/branches/SACLs/twistedcaldav/caldavd.py	2006-12-05 00:05:57 UTC (rev 655)
+++ CalendarServer/branches/SACLs/twistedcaldav/caldavd.py	2006-12-05 00:22:02 UTC (rev 656)
@@ -67,9 +67,15 @@
     'ServerStatsFile': '/Library/CalendarServer/Documents/stats.plist',
     'UserQuotaBytes': 104857600,
     'Verbose': False,
-    'twistdLocation': '/usr/share/caldavd/bin/twistd'}
+    'twistdLocation': '/usr/share/caldavd/bin/twistd',
+    'SACLEnable': False,
+    'SACLService': 'com.apple.access_calendar',
+    }
 
 
+CONFIG = DEFAULTS.copy()
+
+
 class caldavd(object):
     """
     Runs the caldav server.
@@ -79,7 +85,7 @@
         # Option defaults
         self.plistfile = "/etc/caldavd/caldavd.plist"
 
-        self.config = DEFAULTS.copy()
+        self.config = CONFIG
 
         self.action = None
     
@@ -283,6 +289,8 @@
             else:
                 print "Unknown option: %s" % (k,)
 
+        CONFIG = self.config
+
     def validate(self):
         
         result = True

Modified: CalendarServer/branches/SACLs/twistedcaldav/repository.py
===================================================================
--- CalendarServer/branches/SACLs/twistedcaldav/repository.py	2006-12-05 00:05:57 UTC (rev 655)
+++ CalendarServer/branches/SACLs/twistedcaldav/repository.py	2006-12-05 00:22:02 UTC (rev 656)
@@ -46,6 +46,7 @@
 from twisted.web2.dav.element.parser import lookupElement
 from twisted.web2.dav.resource import TwistedACLInheritable
 from twisted.web2.dav.util import joinURL
+from twisted.web2.dav.idav import IDAVPrincipalCollectionResource
 from twisted.web2.log import LogWrapperResource
 from twisted.web2.server import Site
 
@@ -378,22 +379,11 @@
         """
         Build the entire repository starting at the root resource.
         """
-        self.collection.build(self.path, "/", directory)
+        self.collection.build(self, self.path, "/", directory)
         
-        # Setup the principal-collection-set property if required
-        if self.autoPrincipalCollectionSet:
-            # Check that a principal collection was actually created and 'tagged'
-            if not self.principalCollections:
-                log.msg("Cannot create a DAV:principal-collection-set property on the root resource because there are no principal collections.")
-                return
+        # Cheat        
+        self.collection.resource._principalCollections = self.principalCollections
             
-            # Create the private property
-            hrefs = []
-            for collection in self.principalCollections:
-                hrefs.append(davxml.HRef.fromString(collection.uri))
-            pcs = davxml.PrincipalCollectionSet(*hrefs)
-            self.collection.resource.writeDeadProperty(pcs)
-
 class Collection (object):
     """
     Contains information about a collection in the repository.
@@ -482,14 +472,13 @@
                 self.properties.append(Prop())
                 self.properties[-1].parseXML(child)
 
-    def build(self, docroot, urlroot, directory):
+    def build(self, docroot, mypath, urlroot, directory):
         """
         Create this collection, initialising any properties and then create any child
         collections.
         @param docroot: the file system path to create the collection in.
         @param urlroot: the URI path root to create the collection resource in.
         """
-        mypath = docroot
         myurl = urlroot
         if self.name is not None:
             mypath = os.path.join(mypath, self.name)
@@ -529,13 +518,16 @@
         # Set ACL now
         if self.acl is not None:
             self.resource.setAccessControlList(self.acl.acl)
-
+        
         for member in self.members:
-            child = member.build(mypath, myurl, directory)
+            child = member.build(docroot, mypath, myurl, directory)
             # Only putChild if one does not already exists
             if self.resource.putChildren.get(member.name, None) is None:
                 self.resource.putChild(member.name, child)
 
+            if IDAVPrincipalCollectionResource.providedBy(child):
+                docroot.principalCollections.append(child)
+                
         return self.resource
 
 class Prop (object):

Added: CalendarServer/branches/SACLs/twistedcaldav/root.py
===================================================================
--- CalendarServer/branches/SACLs/twistedcaldav/root.py	                        (rev 0)
+++ CalendarServer/branches/SACLs/twistedcaldav/root.py	2006-12-05 00:22:02 UTC (rev 656)
@@ -0,0 +1,101 @@
+##
+# 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: David Reid, dreid at apple.com
+##
+
+from twisted.internet import defer
+from twisted.python.failure import Failure
+from twisted.cred.error import UnauthorizedLogin
+
+from twisted.web2.http import HTTPError
+from twisted.web2.auth.wrapper import UnauthorizedResponse
+
+from twisted.web2.dav import davxml
+from twisted.web2.dav.static import DAVFile
+
+
+class RootResource(DAVFile):
+    """A special root resource that contains support checking SACLs
+    """
+
+    useSacls = False
+    saclService = None
+
+    def __init__(self, path, *args, **kwargs):
+        super(RootResource, self).__init__(path, *args, **kwargs)
+        
+        from twistedcaldav import caldavd
+
+        if caldavd.CONFIG['SACLEnable'] and RootResource.CheckSACL:
+            self.saclService = caldavd.CONFIG['SACLService']
+            self.useSacls = caldavd.CONFIG['SACLEnable']
+
+    def checkSacl(self, request):
+        """Check SACLs against the current request
+        """
+
+        def _authCb((authnUser, authzUser)):
+            # Insure that the user is not unauthenticated.
+            # SACLs are authorization for the use of the service,
+            # so unauthenticated access doesn't make any sense.
+            if authzUser == davxml.Principal(davxml.Unauthenticated()):
+                return Failure(HTTPError(UnauthorizedResponse(
+                            request.credentialFactories,
+                            request.remoteAddr)))
+
+            return (authnUser, authzUser)
+
+        def _authEb(failure):
+            # Make sure we propogate UnauthorizedLogin errors.
+            failure.trap(UnauthorizedLogin)
+
+            return Failure(HTTPError(UnauthorizedResponse(
+                        request.credentialFactories,
+                        request.remoteAddr)))
+
+        def _checkSACLCb((authnUser, authzUser)):
+            # Figure out the "username" from the davxml.Principal object
+            username = authzUser.children[0].children[0].data
+            username = username.split('/')[-1]
+
+            if not RootResource.CheckSACL(username, self.saclService):
+                return Failure(HTTPError(403))
+
+            return True
+            
+        d = defer.maybeDeferred(self.authenticate, request)
+        d.addCallbacks(_authCb, _authEb)
+        d.addCallback(_checkSACLCb)
+        return d
+
+    def locateChild(self, request, segments):
+        if self.useSacls:
+            d = self.checkSacl(request)
+            d.addCallback(lambda _: super(RootResource, self
+                                          ).locateChild(request, segments))
+
+            return d
+
+        return super(RootResource, self).locateChild(request, segments)
+
+
+# So CheckSACL will be parameterized
+# We do this after RootResource is defined
+try:
+    from appleauth import CheckSACL
+    RootResoruce.CheckSACL = CheckSACL
+except ImportError:
+    RootResource.CheckSACL = None

Added: CalendarServer/branches/SACLs/twistedcaldav/test/test_root.py
===================================================================
--- CalendarServer/branches/SACLs/twistedcaldav/test/test_root.py	                        (rev 0)
+++ CalendarServer/branches/SACLs/twistedcaldav/test/test_root.py	2006-12-05 00:22:02 UTC (rev 656)
@@ -0,0 +1,233 @@
+##
+# 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: David Reid, dreid at apple.com
+##
+
+import os
+
+from twistedcaldav.root import RootResource
+
+from twistedcaldav.test.util import TestCase
+from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
+from twistedcaldav.directory.test.test_xmlfile import xmlFile
+
+from twisted.internet import defer
+
+from twisted.web2.http import HTTPError
+
+from twisted.web2.dav import auth
+from twisted.web2.dav import davxml
+
+from twisted.web2 import server
+from twisted.web2.auth import basic
+from twisted.web2 import http_headers
+
+from twisted.cred.portal import Portal
+
+from twisted.web2.test.test_server import SimpleRequest
+
+class FakeCheckSACL(object):
+    def __init__(self, sacls=None):
+        self.sacls = sacls or {}
+
+    def __call__(self, username, service):
+        if service not in self.sacls:
+            return False
+
+        return username in self.sacls[service]
+
+class RootTests(TestCase):
+    def setUp(self):
+        self.docroot = self.mktemp()
+
+        RootResource.saclService = 'com.apple.access_calendar'
+
+        RootResource.CheckSACL = FakeCheckSACL(sacls={
+                'com.apple.access_calendar': ['dreid']})
+
+        directory = XMLDirectoryService(xmlFile)
+
+        principals = DirectoryPrincipalProvisioningResource(
+            os.path.join(self.docroot, 'principals'),
+            '/principals/',
+            directory)
+
+        root = RootResource(self.docroot, 
+                            principalCollections=[principals])
+
+        root.putChild('principals',
+                      principals)
+
+        portal = Portal(auth.DavRealm())
+        portal.registerChecker(directory)
+
+        self.root = auth.AuthenticationWrapper(
+            root, 
+            portal, 
+            credentialFactories=(basic.BasicCredentialFactory("Test realm"),),
+            loginInterfaces=(auth.IPrincipal,))
+
+        self.site = server.Site(self.root)
+
+    def test_noSacls(self):
+        """
+        Test the behaviour of locateChild when SACLs are not enabled.
+        
+        should return a valid resource
+        """
+        self.root.resource.useSacls = False
+
+        request = SimpleRequest(self.site,
+                                "GET",
+                                "/principals/")
+
+        resrc, segments = self.root.locateChild(request,
+                                         ['principals'])
+
+        resrc, segments = resrc.locateChild(request, ['principals'])
+
+        self.failUnless(
+            isinstance(resrc,
+                       DirectoryPrincipalProvisioningResource),
+            "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,))
+
+        self.assertEquals(segments, [])
+
+    def test_inSacls(self):
+        """
+        Test the behavior of locateChild when SACLs are enabled and the 
+        user is in the SACL group
+
+        should return a valid resource
+        """
+        self.root.resource.useSacls = True
+
+        request = SimpleRequest(
+            self.site,
+            "GET",
+            "/principals/",
+            headers=http_headers.Headers({
+                    'Authorization': ['basic', '%s' % (
+                            'dreid:dierd'.encode('base64'),)]}))
+        
+        resrc, segments = self.root.locateChild(request,
+                                         ['principals'])
+
+        def _Cb((resrc, segments)):
+            self.failUnless(
+                isinstance(resrc,
+                           DirectoryPrincipalProvisioningResource),
+                "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,))
+
+            self.assertEquals(segments, [])
+
+            self.assertEquals(request.authzUser, 
+                              davxml.Principal(
+                    davxml.HRef('/principals/user/dreid')))
+            
+        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
+        d.addCallback(_Cb)
+
+        return d
+
+    def test_notInSacls(self):
+        """
+        Test the behavior of locateChild when SACLs are enabled and the
+        user is not in the SACL group
+        
+        should return a 403 forbidden response
+        """
+        self.root.resource.useSacls = True
+
+        request = SimpleRequest(
+            self.site,
+            "GET",
+            "/principals/",
+            headers=http_headers.Headers({
+                    'Authorization': ['basic', '%s' % (
+                            'wsanchez:zehcnasw'.encode('base64'),)]}))
+        
+        resrc, segments = self.root.locateChild(request,
+                                         ['principals'])
+
+        def _Eb(failure):
+            self.assertEquals(failure.value.response.code, 403)
+            
+        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
+        d.addErrback(_Eb)
+
+        return d
+
+    def test_unauthenticated(self):
+        """
+        Test the behavior of locateChild when SACLs are enabled and the request
+        is unauthenticated
+        
+        should return a 401 UnauthorizedResponse
+        """
+
+        self.root.resource.useSacls = True
+        request = SimpleRequest(self.site,
+                                "GET",
+                                "/principals/")
+
+        resrc, segments = self.root.locateChild(request,
+                                                ['principals'])
+
+        def _Cb(result):
+            raise AssertionError(("RootResource.locateChild did not return "
+                                  "an error"))
+
+        def _Eb(failure):
+            failure.trap(HTTPError)
+
+            self.assertEquals(failure.value.response.code, 401)
+
+        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
+
+        d.addCallback(_Cb)
+        d.addErrback(_Eb)
+
+        return d
+
+    def test_badCredentials(self):
+        """
+        Test the behavior of locateChild when SACLS are enabled, and 
+        incorrect credentials are given.
+
+        should return a 401 UnauthorizedResponse
+        """
+        self.root.resource.useSacls = True
+
+        request = SimpleRequest(
+            self.site,
+            "GET",
+            "/principals/",
+            headers=http_headers.Headers({
+                    'Authorization': ['basic', '%s' % (
+                            'dreid:dreid'.encode('base64'),)]}))
+        
+        resrc, segments = self.root.locateChild(request,
+                                         ['principals'])
+
+        def _Eb(failure):
+            self.assertEquals(failure.value.response.code, 401)
+            
+        d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
+        d.addErrback(_Eb)
+
+        return d

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


More information about the calendarserver-changes mailing list