[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