[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