[CalendarServer-changes] [3598] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Jan 19 15:36:19 PST 2009


Revision: 3598
          http://trac.macosforge.org/projects/calendarserver/changeset/3598
Author:   wsanchez at apple.com
Date:     2009-01-19 15:36:19 -0800 (Mon, 19 Jan 2009)
Log Message:
-----------
twistedcaldav.root -> calendarserver.provision.root
twistedcaldav._sacl -> calendarserver.platform.darwin._sacl

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/setup.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/platform/
    CalendarServer/trunk/calendarserver/platform/__init__.py
    CalendarServer/trunk/calendarserver/platform/darwin/
    CalendarServer/trunk/calendarserver/platform/darwin/__init__.py
    CalendarServer/trunk/calendarserver/platform/darwin/_sacl.c
    CalendarServer/trunk/calendarserver/provision/__init__.py
    CalendarServer/trunk/calendarserver/provision/root.py
    CalendarServer/trunk/calendarserver/provision/test/
    CalendarServer/trunk/calendarserver/provision/test/__init__.py
    CalendarServer/trunk/calendarserver/provision/test/test_root.py

Removed Paths:
-------------
    CalendarServer/trunk/twistedcaldav/_sacl.c
    CalendarServer/trunk/twistedcaldav/root.py
    CalendarServer/trunk/twistedcaldav/test/test_root.py

Copied: CalendarServer/trunk/calendarserver/platform/darwin/_sacl.c (from rev 3593, CalendarServer/trunk/twistedcaldav/_sacl.c)
===================================================================
--- CalendarServer/trunk/calendarserver/platform/darwin/_sacl.c	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/platform/darwin/_sacl.c	2009-01-19 23:36:19 UTC (rev 3598)
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2006-2007 Apple 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.
+ */
+
+#include "Python.h"
+#include <Security/Security.h>
+#include <membership.h>
+
+int mbr_check_service_membership(const uuid_t user, const char* servicename, int* ismember);
+
+/*
+    CheckSACL(userOrGroupName, service)
+    Checks user or group membership in a service.
+*/
+static PyObject *appleauth_CheckSACL(PyObject *self, PyObject *args) {
+    char *username;
+    int usernameSize;
+    char *serviceName;
+    int serviceNameSize;
+
+    // get the args
+    if (!PyArg_ParseTuple(args, "s#s#", &username,
+                          &usernameSize, &serviceName, &serviceNameSize)) {
+        return NULL;
+    }
+
+    // get a uuid for the user
+    uuid_t user;
+    int result = mbr_user_name_to_uuid(username, user);
+    int isMember = 0;
+
+    if ( result != 0 ) {
+        // no uuid for the user, we might be a group.
+        result = mbr_group_name_to_uuid(username, user);
+    }
+
+    if ( result != 0 ) {
+        return Py_BuildValue("i", (-1));
+    }
+
+    result = mbr_check_service_membership(user, serviceName, &isMember);
+
+    if ( ( result == 0 && isMember == 1 ) || ( result == ENOENT ) ) {
+        // passed
+        return Py_BuildValue("i", 0);
+    }
+
+    return Py_BuildValue("i", (-2));
+}
+
+/* Method definitions. */
+static struct PyMethodDef _sacl_methods[] = {
+    {"CheckSACL", appleauth_CheckSACL},
+    {NULL, NULL} /* Sentinel */
+};
+
+void init_sacl(void) {
+    Py_InitModule("_sacl", _sacl_methods);
+}

Copied: CalendarServer/trunk/calendarserver/provision/root.py (from rev 3597, CalendarServer/trunk/twistedcaldav/root.py)
===================================================================
--- CalendarServer/trunk/calendarserver/provision/root.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/provision/root.py	2009-01-19 23:36:19 UTC (rev 3598)
@@ -0,0 +1,241 @@
+##
+# Copyright (c) 2005-2007 Apple 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.
+##
+
+__all__ = [
+    "RootACLMixIn",
+    "RootResource",
+]
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.cred.error import LoginFailed, UnauthorizedLogin
+
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.http import HTTPError, StatusResponse
+from twisted.web2.auth.wrapper import UnauthorizedResponse
+from twisted.web.xmlrpc import Proxy
+
+from twistedcaldav.extensions import DAVFile, CachingXattrPropertyStore
+from twistedcaldav.extensions import DirectoryPrincipalPropertySearchMixIn
+from twistedcaldav.extensions import ReadOnlyResourceMixIn
+from twistedcaldav.config import config
+from twistedcaldav.log import Logger
+from twistedcaldav.cache import _CachedResponseResource
+from twistedcaldav.cache import MemcacheResponseCache, MemcacheChangeNotifier
+from twistedcaldav.cache import DisabledCache
+from twistedcaldav.static import CalendarHomeFile
+from twistedcaldav.directory.principal import DirectoryPrincipalResource
+
+log = Logger()
+
+
+class RootResource (ReadOnlyResourceMixIn, DirectoryPrincipalPropertySearchMixIn, DAVFile):
+    """
+    A special root resource that contains support checking SACLs
+    as well as adding responseFilters.
+    """
+
+    useSacls = False
+    saclService = "calendar"
+
+    def __init__(self, path, *args, **kwargs):
+        super(RootResource, self).__init__(path, *args, **kwargs)
+
+        if config.EnableSACLs:
+            if RootResource.CheckSACL:
+                self.useSacls = True
+            else:
+                log.msg(("RootResource.CheckSACL is unset but "
+                         "config.EnableSACLs is True, SACLs will not be "
+                         "turned on."))
+
+        self.contentFilters = []
+
+        if config.Memcached["ClientEnabled"]:
+            self.responseCache = MemcacheResponseCache(self.fp)
+
+            CalendarHomeFile.cacheNotifierFactory = MemcacheChangeNotifier
+            DirectoryPrincipalResource.cacheNotifierFactory = MemcacheChangeNotifier
+        else:
+            self.responseCache = DisabledCache()
+
+        if config.ResponseCompression:
+            from twisted.web2.filter import gzip
+            self.contentFilters.append((gzip.gzipfilter, True))
+
+        if not config.EnableKeepAlive:
+            def addConnectionClose(request, response):
+                response.headers.setHeader("connection", ("close",))
+                request.chanRequest.channel.setReadPersistent(False)
+                return response
+            self.contentFilters.append((addConnectionClose, True))
+
+
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = CachingXattrPropertyStore(self)
+
+        return self._dead_properties
+
+    def defaultAccessControlList(self):
+        return config.RootResourceACL
+
+    @inlineCallbacks
+    def checkSacl(self, request):
+        """
+        Check SACLs against the current request
+        """
+
+        try:
+            authnUser, authzUser = yield self.authenticate(request)
+        except Exception:
+            response = (yield UnauthorizedResponse.makeResponse(
+                request.credentialFactories,
+                request.remoteAddr
+            ))
+            raise HTTPError(response)
+
+        # Ensure 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()):
+            log.msg("Unauthenticated users not enabled with the %r SACL" % (self.saclService,))
+            response = (yield UnauthorizedResponse.makeResponse(
+                request.credentialFactories,
+                request.remoteAddr
+            ))
+            raise HTTPError(response)
+
+        # Cache the authentication details
+        request.authnUser = authnUser
+        request.authzUser = authzUser
+
+        # Figure out the "username" from the davxml.Principal object
+        request.checkingSACL = True
+        principal = (yield request.locateResource(authzUser.children[0].children[0].data))
+        delattr(request, "checkingSACL")
+        username = principal.record.shortName
+
+        if RootResource.CheckSACL(username, self.saclService) != 0:
+            log.msg("User %r is not enabled with the %r SACL" % (username, self.saclService,))
+            raise HTTPError(responsecode.FORBIDDEN)
+
+        # Mark SACLs as having been checked so we can avoid doing it multiple times
+        request.checkedSACL = True
+
+
+        returnValue(True)
+
+    @inlineCallbacks
+    def locateChild(self, request, segments):
+
+        for filter in self.contentFilters:
+            request.addResponseFilter(filter[0], atEnd=filter[1])
+
+        # Examine cookies for wiki auth token
+        wikiConfig = config.Authentication.Wiki
+        cookies = request.headers.getHeader("cookie")
+        if wikiConfig["Enabled"] and cookies is not None:
+            for cookie in cookies:
+                if cookie.name == wikiConfig["Cookie"]:
+                    token = cookie.value
+                    break
+            else:
+                token = None
+
+            if token is not None and token != "unauthenticated":
+                log.info("Wiki sessionID cookie value: %s" % (token,))
+                proxy = Proxy(wikiConfig["URL"])
+                try:
+                    username = (yield proxy.callRemote(wikiConfig["UserMethod"], token))
+                    log.info("Wiki lookup returned user: %s" % (username,))
+                    directory = request.site.resource.getDirectory()
+                    record = directory.recordWithShortName("users", username)
+                    if record is None:
+                        raise HTTPError(StatusResponse(
+                            responsecode.FORBIDDEN,
+                            "The username (%s) corresponding to your sessionID was not found by calendar server." % (username,)
+                        ))
+                    request.authnUser = request.authzUser = davxml.Principal(
+                        davxml.HRef.fromString("/principals/__uids__/%s/" % (record.guid,)))
+                    child = (yield super(RootResource, self).locateChild(request, segments))
+                    returnValue(child)
+
+                except Exception, e:
+                    log.info("Wiki lookup returned ERROR: %s" % (e,))
+                    raise HTTPError(StatusResponse(
+                        responsecode.FORBIDDEN,
+                        "Your sessionID was rejected by the authenticating wiki server."
+                    ))
+
+
+        if self.useSacls and not hasattr(request, "checkedSACL") and not hasattr(request, "checkingSACL"):
+            yield self.checkSacl(request)
+            child = (yield super(RootResource, self).locateChild(request, segments))
+            returnValue(child)
+
+        if config.RejectClients:
+            #
+            # Filter out unsupported clients
+            #
+            agent = request.headers.getHeader("user-agent")
+            if agent is not None:
+                for reject in config.RejectClients:
+                    if reject.search(agent) is not None:
+                        log.info("Rejecting user-agent: %s" % (agent,))
+                        raise HTTPError(StatusResponse(
+                            responsecode.FORBIDDEN,
+                            "Your client software (%s) is not allowed to access this service." % (agent,)
+                        ))
+
+        if request.method == "PROPFIND" and not getattr(request, "notInCache", False):
+            try:
+                authnUser, authzUser = (yield self.authenticate(request))
+                request.authnUser = authnUser
+                request.authzUser = authzUser
+            except (UnauthorizedLogin, LoginFailed):
+                response = (yield UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr
+                ))
+                raise HTTPError(response)
+
+            try:
+                if not getattr(request, "checkingCache", False):
+                    request.checkingCache = True
+                    response = (yield self.responseCache.getResponseForRequest(request))
+                    if response is None:
+                        request.notInCache = True
+                        raise KeyError("Not found in cache.")
+        
+                    returnValue((_CachedResponseResource(response), []))
+            except KeyError:
+                pass
+
+        child = (yield super(RootResource, self).locateChild(request, segments))
+        returnValue(child)
+
+    def http_COPY       (self, request): return responsecode.FORBIDDEN
+    def http_MOVE       (self, request): return responsecode.FORBIDDEN
+    def http_DELETE     (self, request): return responsecode.FORBIDDEN
+
+# So CheckSACL will be parameterized
+# We do this after RootResource is defined
+try:
+    from twistedcaldav._sacl import CheckSACL
+    RootResource.CheckSACL = CheckSACL
+except ImportError:
+    RootResource.CheckSACL = None

Copied: CalendarServer/trunk/calendarserver/provision/test/test_root.py (from rev 3593, CalendarServer/trunk/twistedcaldav/test/test_root.py)
===================================================================
--- CalendarServer/trunk/calendarserver/provision/test/test_root.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/provision/test/test_root.py	2009-01-19 23:36:19 UTC (rev 3598)
@@ -0,0 +1,246 @@
+##
+# Copyright (c) 2005-2007 Apple 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.
+##
+
+import os
+
+from twisted.cred.portal import Portal
+from twisted.internet.defer import inlineCallbacks, maybeDeferred
+from twisted.web2 import http_headers
+from twisted.web2 import responsecode
+from twisted.web2 import server
+from twisted.web2.auth import basic
+from twisted.web2.dav import auth
+from twisted.web2.dav import davxml
+from twisted.web2.http import HTTPError
+from twisted.web2.iweb import IResponse
+from twisted.web2.test.test_server import SimpleRequest
+
+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 calendarserver.provision.root import RootResource
+
+class FakeCheckSACL(object):
+    def __init__(self, sacls=None):
+        self.sacls = sacls or {}
+
+    def __call__(self, username, service):
+        if service not in self.sacls:
+            return 1
+
+        if username in self.sacls[service]:
+            return 0
+
+        return 1
+
+class RootTests(TestCase):
+    def setUp(self):
+        self.docroot = self.mktemp()
+        os.mkdir(self.docroot)
+
+        RootResource.CheckSACL = FakeCheckSACL(sacls={
+                'calendar': ['dreid']})
+
+        directory = XMLDirectoryService(xmlFile)
+
+        principals = DirectoryPrincipalProvisioningResource('/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)
+
+    @inlineCallbacks
+    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
+
+        resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+
+        self.failUnless(
+            isinstance(resrc, DirectoryPrincipalProvisioningResource),
+            "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,)
+        )
+
+        self.assertEquals(segments, [])
+
+    @inlineCallbacks
+    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
+
+        resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+
+        self.failUnless(
+            isinstance(resrc, DirectoryPrincipalProvisioningResource),
+            "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,)
+        )
+
+        self.assertEquals(segments, [])
+
+        self.assertEquals(request.authzUser,
+                          davxml.Principal(
+                davxml.HRef('/principals/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/')))
+
+    @inlineCallbacks
+    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
+
+        try:
+            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+        except HTTPError, e:
+            self.assertEquals(e.response.code, 403)
+
+    @inlineCallbacks
+    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
+
+        try:
+            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+            raise AssertionError(("RootResource.locateChild did not return an error"))
+        except HTTPError, e:
+            self.assertEquals(e.response.code, 401)
+
+    @inlineCallbacks
+    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
+
+        try:
+            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
+        except HTTPError, e:
+            self.assertEquals(e.response.code, 401)
+
+    def test_DELETE(self):
+        def do_test(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.FORBIDDEN:
+                self.fail("Incorrect response for DELETE /: %s" % (response.code,))
+
+        request = SimpleRequest(self.site, "DELETE", "/")
+        return self.send(request, do_test)
+
+    def test_COPY(self):
+        def do_test(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.FORBIDDEN:
+                self.fail("Incorrect response for COPY /: %s" % (response.code,))
+
+        request = SimpleRequest(
+            self.site,
+            "COPY",
+            "/",
+            headers=http_headers.Headers({"Destination":"/copy/"})
+        )
+        return self.send(request, do_test)
+
+    def test_MOVE(self):
+        def do_test(response):
+            response = IResponse(response)
+
+            if response.code != responsecode.FORBIDDEN:
+                self.fail("Incorrect response for MOVE /: %s" % (response.code,))
+
+        request = SimpleRequest(
+            self.site,
+            "MOVE",
+            "/",
+            headers=http_headers.Headers({"Destination":"/copy/"})
+        )
+        return self.send(request, do_test)

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2009-01-19 23:23:00 UTC (rev 3597)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2009-01-19 23:36:19 UTC (rev 3598)
@@ -58,7 +58,6 @@
 from twistedcaldav.accesslog import AMPCommonAccessLoggingObserver
 from twistedcaldav.config import config, defaultConfig, defaultConfigFile
 from twistedcaldav.config import ConfigurationError
-from twistedcaldav.root import RootResource
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
@@ -83,6 +82,8 @@
 except ImportError:
     NegotiateCredentialFactory = None
 
+from calendarserver.provision.root import RootResource
+
 log = Logger()
 
 

Modified: CalendarServer/trunk/setup.py
===================================================================
--- CalendarServer/trunk/setup.py	2009-01-19 23:23:00 UTC (rev 3597)
+++ CalendarServer/trunk/setup.py	2009-01-19 23:36:19 UTC (rev 3598)
@@ -100,10 +100,10 @@
 if sys.platform == "darwin":
     extensions.append(
         Extension(
-            "twistedcaldav._sacl",
+            "calendarserver.platform.darwin._sacl",
             extra_compile_args = ["-arch", "ppc", "-arch", "i386"],
             extra_link_args = ["-framework", "Security", "-arch", "ppc", "-arch", "i386"],
-            sources = ["twistedcaldav/_sacl.c"]
+            sources = ["calendarserver/platform/darwin/_sacl.c"]
         )
     )
 
@@ -126,6 +126,9 @@
     platforms        = [ "all" ],
     packages         = [
                          "calendarserver",
+                         "calendarserver.platform",
+                         "calendarserver.platform.darwin",
+                         "calendarserver.provision",
                          "calendarserver.tap",
                          "calendarserver.tap.test",
                          "calendarserver.test",

Deleted: CalendarServer/trunk/twistedcaldav/_sacl.c
===================================================================
--- CalendarServer/trunk/twistedcaldav/_sacl.c	2009-01-19 23:23:00 UTC (rev 3597)
+++ CalendarServer/trunk/twistedcaldav/_sacl.c	2009-01-19 23:36:19 UTC (rev 3598)
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) 2006-2007 Apple 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.
- */
-
-#include "Python.h"
-#include <Security/Security.h>
-#include <membership.h>
-
-int mbr_check_service_membership(const uuid_t user, const char* servicename, int* ismember);
-
-/*
-    CheckSACL(userOrGroupName, service)
-    Checks user or group membership in a service.
-*/
-static PyObject *appleauth_CheckSACL(PyObject *self, PyObject *args) {
-    char *username;
-    int usernameSize;
-    char *serviceName;
-    int serviceNameSize;
-
-    // get the args
-    if (!PyArg_ParseTuple(args, "s#s#", &username,
-                          &usernameSize, &serviceName, &serviceNameSize)) {
-        return NULL;
-    }
-
-    // get a uuid for the user
-    uuid_t user;
-    int result = mbr_user_name_to_uuid(username, user);
-    int isMember = 0;
-
-    if ( result != 0 ) {
-        // no uuid for the user, we might be a group.
-        result = mbr_group_name_to_uuid(username, user);
-    }
-
-    if ( result != 0 ) {
-        return Py_BuildValue("i", (-1));
-    }
-
-    result = mbr_check_service_membership(user, serviceName, &isMember);
-
-    if ( ( result == 0 && isMember == 1 ) || ( result == ENOENT ) ) {
-        // passed
-        return Py_BuildValue("i", 0);
-    }
-
-    return Py_BuildValue("i", (-2));
-}
-
-/* Method definitions. */
-static struct PyMethodDef _sacl_methods[] = {
-    {"CheckSACL", appleauth_CheckSACL},
-    {NULL, NULL} /* Sentinel */
-};
-
-void init_sacl(void) {
-    Py_InitModule("_sacl", _sacl_methods);
-}

Deleted: CalendarServer/trunk/twistedcaldav/root.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/root.py	2009-01-19 23:23:00 UTC (rev 3597)
+++ CalendarServer/trunk/twistedcaldav/root.py	2009-01-19 23:36:19 UTC (rev 3598)
@@ -1,239 +0,0 @@
-##
-# Copyright (c) 2005-2007 Apple 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.
-##
-
-__all__ = [
-    "RootACLMixIn",
-    "RootResource",
-]
-
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twisted.cred.error import LoginFailed, UnauthorizedLogin
-
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError, StatusResponse
-from twisted.web2.auth.wrapper import UnauthorizedResponse
-from twisted.web.xmlrpc import Proxy
-
-from twistedcaldav.extensions import DAVFile, CachingXattrPropertyStore
-from twistedcaldav.extensions import DirectoryPrincipalPropertySearchMixIn
-from twistedcaldav.config import config
-from twistedcaldav.log import Logger
-from twistedcaldav.cache import _CachedResponseResource
-from twistedcaldav.cache import MemcacheResponseCache, MemcacheChangeNotifier
-from twistedcaldav.cache import DisabledCache
-from twistedcaldav.static import CalendarHomeFile
-from twistedcaldav.directory.principal import DirectoryPrincipalResource
-
-log = Logger()
-
-class RootResource (ReadOnlyResourceMixIn, DirectoryPrincipalPropertySearchMixIn, DAVFile):
-    """
-    A special root resource that contains support checking SACLs
-    as well as adding responseFilters.
-    """
-
-    useSacls = False
-    saclService = "calendar"
-
-    def __init__(self, path, *args, **kwargs):
-        super(RootResource, self).__init__(path, *args, **kwargs)
-
-        if config.EnableSACLs:
-            if RootResource.CheckSACL:
-                self.useSacls = True
-            else:
-                log.msg(("RootResource.CheckSACL is unset but "
-                         "config.EnableSACLs is True, SACLs will not be "
-                         "turned on."))
-
-        self.contentFilters = []
-
-        if config.Memcached["ClientEnabled"]:
-            self.responseCache = MemcacheResponseCache(self.fp)
-
-            CalendarHomeFile.cacheNotifierFactory = MemcacheChangeNotifier
-            DirectoryPrincipalResource.cacheNotifierFactory = MemcacheChangeNotifier
-        else:
-            self.responseCache = DisabledCache()
-
-        if config.ResponseCompression:
-            from twisted.web2.filter import gzip
-            self.contentFilters.append((gzip.gzipfilter, True))
-
-        if not config.EnableKeepAlive:
-            def addConnectionClose(request, response):
-                response.headers.setHeader("connection", ("close",))
-                request.chanRequest.channel.setReadPersistent(False)
-                return response
-            self.contentFilters.append((addConnectionClose, True))
-
-
-    def deadProperties(self):
-        if not hasattr(self, "_dead_properties"):
-            self._dead_properties = CachingXattrPropertyStore(self)
-
-        return self._dead_properties
-
-    def defaultAccessControlList(self):
-        return config.RootResourceACL
-
-    @inlineCallbacks
-    def checkSacl(self, request):
-        """
-        Check SACLs against the current request
-        """
-
-        try:
-            authnUser, authzUser = yield self.authenticate(request)
-        except Exception:
-            response = (yield UnauthorizedResponse.makeResponse(
-                request.credentialFactories,
-                request.remoteAddr
-            ))
-            raise HTTPError(response)
-
-        # Ensure 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()):
-            log.msg("Unauthenticated users not enabled with the %r SACL" % (self.saclService,))
-            response = (yield UnauthorizedResponse.makeResponse(
-                request.credentialFactories,
-                request.remoteAddr
-            ))
-            raise HTTPError(response)
-
-        # Cache the authentication details
-        request.authnUser = authnUser
-        request.authzUser = authzUser
-
-        # Figure out the "username" from the davxml.Principal object
-        request.checkingSACL = True
-        principal = (yield request.locateResource(authzUser.children[0].children[0].data))
-        delattr(request, "checkingSACL")
-        username = principal.record.shortName
-
-        if RootResource.CheckSACL(username, self.saclService) != 0:
-            log.msg("User %r is not enabled with the %r SACL" % (username, self.saclService,))
-            raise HTTPError(responsecode.FORBIDDEN)
-
-        # Mark SACLs as having been checked so we can avoid doing it multiple times
-        request.checkedSACL = True
-
-
-        returnValue(True)
-
-    @inlineCallbacks
-    def locateChild(self, request, segments):
-
-        for filter in self.contentFilters:
-            request.addResponseFilter(filter[0], atEnd=filter[1])
-
-        # Examine cookies for wiki auth token
-        wikiConfig = config.Authentication.Wiki
-        cookies = request.headers.getHeader("cookie")
-        if wikiConfig["Enabled"] and cookies is not None:
-            for cookie in cookies:
-                if cookie.name == wikiConfig["Cookie"]:
-                    token = cookie.value
-                    break
-            else:
-                token = None
-
-            if token is not None and token != "unauthenticated":
-                log.info("Wiki sessionID cookie value: %s" % (token,))
-                proxy = Proxy(wikiConfig["URL"])
-                try:
-                    username = (yield proxy.callRemote(wikiConfig["UserMethod"], token))
-                    log.info("Wiki lookup returned user: %s" % (username,))
-                    directory = request.site.resource.getDirectory()
-                    record = directory.recordWithShortName("users", username)
-                    if record is None:
-                        raise HTTPError(StatusResponse(
-                            responsecode.FORBIDDEN,
-                            "The username (%s) corresponding to your sessionID was not found by calendar server." % (username,)
-                        ))
-                    request.authnUser = request.authzUser = davxml.Principal(
-                        davxml.HRef.fromString("/principals/__uids__/%s/" % (record.guid,)))
-                    child = (yield super(RootResource, self).locateChild(request, segments))
-                    returnValue(child)
-
-                except Exception, e:
-                    log.info("Wiki lookup returned ERROR: %s" % (e,))
-                    raise HTTPError(StatusResponse(
-                        responsecode.FORBIDDEN,
-                        "Your sessionID was rejected by the authenticating wiki server."
-                    ))
-
-
-        if self.useSacls and not hasattr(request, "checkedSACL") and not hasattr(request, "checkingSACL"):
-            yield self.checkSacl(request)
-            child = (yield super(RootResource, self).locateChild(request, segments))
-            returnValue(child)
-
-        if config.RejectClients:
-            #
-            # Filter out unsupported clients
-            #
-            agent = request.headers.getHeader("user-agent")
-            if agent is not None:
-                for reject in config.RejectClients:
-                    if reject.search(agent) is not None:
-                        log.info("Rejecting user-agent: %s" % (agent,))
-                        raise HTTPError(StatusResponse(
-                            responsecode.FORBIDDEN,
-                            "Your client software (%s) is not allowed to access this service." % (agent,)
-                        ))
-
-        if request.method == "PROPFIND" and not getattr(request, "notInCache", False):
-            try:
-                authnUser, authzUser = (yield self.authenticate(request))
-                request.authnUser = authnUser
-                request.authzUser = authzUser
-            except (UnauthorizedLogin, LoginFailed):
-                response = (yield UnauthorizedResponse.makeResponse(
-                    request.credentialFactories,
-                    request.remoteAddr
-                ))
-                raise HTTPError(response)
-
-            try:
-                if not getattr(request, "checkingCache", False):
-                    request.checkingCache = True
-                    response = (yield self.responseCache.getResponseForRequest(request))
-                    if response is None:
-                        request.notInCache = True
-                        raise KeyError("Not found in cache.")
-        
-                    returnValue((_CachedResponseResource(response), []))
-            except KeyError:
-                pass
-
-        child = (yield super(RootResource, self).locateChild(request, segments))
-        returnValue(child)
-
-    def http_COPY       (self, request): return responsecode.FORBIDDEN
-    def http_MOVE       (self, request): return responsecode.FORBIDDEN
-    def http_DELETE     (self, request): return responsecode.FORBIDDEN
-
-# So CheckSACL will be parameterized
-# We do this after RootResource is defined
-try:
-    from twistedcaldav._sacl import CheckSACL
-    RootResource.CheckSACL = CheckSACL
-except ImportError:
-    RootResource.CheckSACL = None

Deleted: CalendarServer/trunk/twistedcaldav/test/test_root.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_root.py	2009-01-19 23:23:00 UTC (rev 3597)
+++ CalendarServer/trunk/twistedcaldav/test/test_root.py	2009-01-19 23:36:19 UTC (rev 3598)
@@ -1,248 +0,0 @@
-##
-# Copyright (c) 2005-2007 Apple 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.
-##
-
-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.cred.portal import Portal
-
-from twisted.internet.defer import inlineCallbacks, maybeDeferred
-
-from twisted.web2 import http_headers
-from twisted.web2 import responsecode
-from twisted.web2 import server
-from twisted.web2.auth import basic
-from twisted.web2.dav import auth
-from twisted.web2.dav import davxml
-from twisted.web2.http import HTTPError
-from twisted.web2.iweb import IResponse
-
-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 1
-
-        if username in self.sacls[service]:
-            return 0
-
-        return 1
-
-class RootTests(TestCase):
-    def setUp(self):
-        self.docroot = self.mktemp()
-        os.mkdir(self.docroot)
-
-        RootResource.CheckSACL = FakeCheckSACL(sacls={
-                'calendar': ['dreid']})
-
-        directory = XMLDirectoryService(xmlFile)
-
-        principals = DirectoryPrincipalProvisioningResource('/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)
-
-    @inlineCallbacks
-    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
-
-        resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
-
-        self.failUnless(
-            isinstance(resrc, DirectoryPrincipalProvisioningResource),
-            "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,)
-        )
-
-        self.assertEquals(segments, [])
-
-    @inlineCallbacks
-    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
-
-        resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
-
-        self.failUnless(
-            isinstance(resrc, DirectoryPrincipalProvisioningResource),
-            "Did not get a DirectoryPrincipalProvisioningResource: %s" % (resrc,)
-        )
-
-        self.assertEquals(segments, [])
-
-        self.assertEquals(request.authzUser,
-                          davxml.Principal(
-                davxml.HRef('/principals/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/')))
-
-    @inlineCallbacks
-    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
-
-        try:
-            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
-        except HTTPError, e:
-            self.assertEquals(e.response.code, 403)
-
-    @inlineCallbacks
-    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
-
-        try:
-            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
-            raise AssertionError(("RootResource.locateChild did not return an error"))
-        except HTTPError, e:
-            self.assertEquals(e.response.code, 401)
-
-    @inlineCallbacks
-    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 = (yield maybeDeferred(self.root.locateChild, request, ['principals']))
-
-        try:
-            resrc, segments = (yield maybeDeferred(resrc.locateChild, request, ['principals']))
-        except HTTPError, e:
-            self.assertEquals(e.response.code, 401)
-
-    def test_DELETE(self):
-        def do_test(response):
-            response = IResponse(response)
-
-            if response.code != responsecode.FORBIDDEN:
-                self.fail("Incorrect response for DELETE /: %s" % (response.code,))
-
-        request = SimpleRequest(self.site, "DELETE", "/")
-        return self.send(request, do_test)
-
-    def test_COPY(self):
-        def do_test(response):
-            response = IResponse(response)
-
-            if response.code != responsecode.FORBIDDEN:
-                self.fail("Incorrect response for COPY /: %s" % (response.code,))
-
-        request = SimpleRequest(
-            self.site,
-            "COPY",
-            "/",
-            headers=http_headers.Headers({"Destination":"/copy/"})
-        )
-        return self.send(request, do_test)
-
-    def test_MOVE(self):
-        def do_test(response):
-            response = IResponse(response)
-
-            if response.code != responsecode.FORBIDDEN:
-                self.fail("Incorrect response for MOVE /: %s" % (response.code,))
-
-        request = SimpleRequest(
-            self.site,
-            "MOVE",
-            "/",
-            headers=http_headers.Headers({"Destination":"/copy/"})
-        )
-        return self.send(request, do_test)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090119/b572dbe2/attachment-0001.html>


More information about the calendarserver-changes mailing list