[CalendarServer-changes] [8955] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri Mar 30 15:08:34 PDT 2012
Revision: 8955
http://trac.macosforge.org/projects/calendarserver/changeset/8955
Author: wsanchez at apple.com
Date: 2012-03-30 15:08:34 -0700 (Fri, 30 Mar 2012)
Log Message:
-----------
Remove sudoers functionality.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tap/util.py
CalendarServer/trunk/twext/web2/dav/resource.py
CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist
CalendarServer/trunk/twistedcaldav/extensions.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
Removed Paths:
-------------
CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
CalendarServer/trunk/twistedcaldav/directory/sudo.py
CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py
Deleted: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2012-03-30 22:08:34 UTC (rev 8955)
@@ -1,1230 +0,0 @@
-##
-# Copyright (c) 2007-2010 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 sys
-import os
-import stat
-import grp
-
-from os.path import dirname, abspath
-
-from zope.interface import implements
-
-from twisted.python.threadable import isInIOThread
-from twisted.internet.reactor import callFromThread
-from twisted.python.usage import Options, UsageError
-from twisted.python.reflect import namedAny
-from twisted.python import log
-
-from twisted.internet.interfaces import IProcessTransport, IReactorProcess
-from twisted.internet.protocol import ServerFactory
-from twisted.internet.defer import Deferred
-from twisted.internet.task import Clock
-
-from twisted.application.service import IService, IServiceCollection
-from twisted.application import internet
-
-from twext.web2.dav import auth
-from twext.web2.log import LogWrapperResource
-from twext.python.filepath import CachingFilePath as FilePath
-
-from twext.python.plistlib import writePlist #@UnresolvedImport
-from twext.internet.tcp import MaxAcceptTCPServer, MaxAcceptSSLServer
-
-from twistedcaldav.config import config, ConfigDict
-from twistedcaldav.stdconfig import DEFAULT_CONFIG
-
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.test.util import TestCase
-
-from calendarserver.tap.caldav import (
- CalDAVOptions, CalDAVServiceMaker, CalDAVService, GroupOwnedUNIXServer,
- DelayedStartupProcessMonitor, DelayedStartupLineLogger, TwistdSlaveProcess
-)
-from calendarserver.provision.root import RootResource
-from StringIO import StringIO
-
-
-# Points to top of source tree.
-sourceRoot = dirname(dirname(dirname(dirname(abspath(__file__)))))
-
-
-class NotAProcessTransport(object):
- """
- Simple L{IProcessTransport} stub.
- """
- implements(IProcessTransport)
-
- def __init__(self, processProtocol, executable, args, env, path,
- uid, gid, usePTY, childFDs):
- """
- Hold on to all the attributes passed to spawnProcess.
- """
- self.processProtocol = processProtocol
- self.executable = executable
- self.args = args
- self.env = env
- self.path = path
- self.uid = uid
- self.gid = gid
- self.usePTY = usePTY
- self.childFDs = childFDs
-
-
-class InMemoryProcessSpawner(Clock):
- """
- Stub out L{IReactorProcess} and L{IReactorClock} so that we can examine the
- interaction of L{DelayedStartupProcessMonitor} and the reactor.
- """
- implements(IReactorProcess)
-
- def __init__(self):
- """
- Create some storage to hold on to all the fake processes spawned.
- """
- super(InMemoryProcessSpawner, self).__init__()
- self.processTransports = []
- self.waiting = []
-
-
- def waitForOneProcess(self, amount=10.0):
- """
- Wait for an L{IProcessTransport} to be created by advancing the clock.
- If none are created in the specified amount of time, raise an
- AssertionError.
- """
- self.advance(amount)
- if self.processTransports:
- return self.processTransports.pop(0)
- else:
- raise AssertionError(
- "There were no process transports available. Calls: " +
- repr(self.calls)
- )
-
-
- def spawnProcess(self, processProtocol, executable, args=(), env={},
- path=None, uid=None, gid=None, usePTY=0,
- childFDs=None):
-
- transport = NotAProcessTransport(
- processProtocol, executable, args, env, path, uid, gid, usePTY,
- childFDs
- )
- transport.startedAt = self.seconds()
- self.processTransports.append(transport)
- if self.waiting:
- self.waiting.pop(0).callback(transport)
- return transport
-
-
-
-class TestCalDAVOptions (CalDAVOptions):
- """
- A fake implementation of CalDAVOptions that provides
- empty implementations of checkDirectory and checkFile.
- """
- def checkDirectory(self, *args, **kwargs):
- pass
-
- def checkFile(self, *args, **kwargs):
- pass
-
-
- def loadConfiguration(self):
- """
- Simple wrapper to avoid printing during test runs.
- """
- oldout = sys.stdout
- newout = sys.stdout = StringIO()
- try:
- return CalDAVOptions.loadConfiguration(self)
- finally:
- sys.stdout = oldout
- log.msg(
- "load configuration console output: %s" % newout.getvalue()
- )
-
-
-class CalDAVOptionsTest (TestCase):
- """
- Test various parameters of our usage.Options subclass
- """
- def setUp(self):
- """
- Set up our options object, giving it a parent, and forcing the
- global config to be loaded from defaults.
- """
- TestCase.setUp(self)
- self.config = TestCalDAVOptions()
- self.config.parent = Options()
- self.config.parent["uid"] = 0
- self.config.parent["gid"] = 0
- self.config.parent["nodaemon"] = False
-
- def tearDown(self):
- config.setDefaults(DEFAULT_CONFIG)
- config.reload()
-
- def test_overridesConfig(self):
- """
- Test that values on the command line's -o and --option options
- overide the config file
- """
- myConfig = ConfigDict(DEFAULT_CONFIG)
- myConfigFile = self.mktemp()
- writePlist(myConfig, myConfigFile)
-
- argv = [
- "-f", myConfigFile,
- "-o", "EnableSACLs",
- "-o", "HTTPPort=80",
- "-o", "BindAddresses=127.0.0.1,127.0.0.2,127.0.0.3",
- "-o", "DocumentRoot=/dev/null",
- "-o", "UserName=None",
- "-o", "EnableProxyPrincipals=False",
- ]
-
- self.config.parseOptions(argv)
-
- self.assertEquals(config.EnableSACLs, True)
- self.assertEquals(config.HTTPPort, 80)
- self.assertEquals(config.BindAddresses,
- ["127.0.0.1", "127.0.0.2", "127.0.0.3"])
- self.assertEquals(config.DocumentRoot, "/dev/null")
- self.assertEquals(config.UserName, None)
- self.assertEquals(config.EnableProxyPrincipals, False)
-
- argv = ["-o", "Authentication=This Doesn't Matter"]
-
- self.assertRaises(UsageError, self.config.parseOptions, argv)
-
- def test_setsParent(self):
- """
- Test that certain values are set on the parent (i.e. twistd's
- Option's object)
- """
- myConfig = ConfigDict(DEFAULT_CONFIG)
- myConfigFile = self.mktemp()
- writePlist(myConfig, myConfigFile)
-
- argv = [
- "-f", myConfigFile,
- "-o", "PIDFile=/dev/null",
- ]
-
- self.config.parseOptions(argv)
-
- self.assertEquals(self.config.parent["pidfile"], "/dev/null")
-
- def test_specifyConfigFile(self):
- """
- Test that specifying a config file from the command line
- loads the global config with those values properly.
- """
- myConfig = ConfigDict(DEFAULT_CONFIG)
-
- myConfig.Authentication.Basic.Enabled = False
- myConfig.HTTPPort = 80
- myConfig.ServerHostName = "calendar.calenderserver.org"
-
- myConfigFile = self.mktemp()
- writePlist(myConfig, myConfigFile)
-
- args = ["-f", myConfigFile]
-
- self.config.parseOptions(args)
-
- self.assertEquals(config.ServerHostName, myConfig["ServerHostName"])
- self.assertEquals(config.HTTPPort, myConfig.HTTPPort)
- self.assertEquals(
- config.Authentication.Basic.Enabled,
- myConfig.Authentication.Basic.Enabled
- )
-
- def test_specifyDictPath(self):
- """
- Test that we can specify command line overrides to leafs using
- a "/" seperated path. Such as "-o MultiProcess/ProcessCount=1"
- """
- myConfig = ConfigDict(DEFAULT_CONFIG)
- myConfigFile = self.mktemp()
- writePlist(myConfig, myConfigFile)
-
- argv = [
- "-o", "MultiProcess/ProcessCount=102",
- "-f", myConfigFile,
- ]
-
- self.config.parseOptions(argv)
-
- self.assertEquals(config.MultiProcess["ProcessCount"], 102)
-
-class BaseServiceMakerTests(TestCase):
- """
- Utility class for ServiceMaker tests.
- """
- configOptions = None
-
- def setUp(self):
- TestCase.setUp(self)
- self.options = TestCalDAVOptions()
- self.options.parent = Options()
- self.options.parent["gid"] = None
- self.options.parent["uid"] = None
- self.options.parent["nodaemon"] = None
-
- self.config = ConfigDict(DEFAULT_CONFIG)
-
- accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml")
- resourcesFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/resources.xml")
- augmentsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/augments.xml")
- pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
-
- self.config["DirectoryService"] = {
- "params": {"xmlFile": accountsFile},
- "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService"
- }
-
- self.config["ResourceService"] = {
- "params": {"xmlFile": resourcesFile},
- }
-
- self.config["AugmentService"] = {
- "params": {"xmlFiles": [augmentsFile]},
- "type": "twistedcaldav.directory.augment.AugmentXMLDB"
- }
-
- self.config.UseDatabase = False
- self.config.ServerRoot = self.mktemp()
- self.config.ConfigRoot = "config"
- self.config.ProcessType = "Single"
- self.config.SSLPrivateKey = pemFile
- self.config.SSLCertificate = pemFile
- self.config.EnableSSL = True
- self.config.Memcached.Pools.Default.ClientEnabled = False
- self.config.Memcached.Pools.Default.ServerEnabled = False
- self.config.DirectoryAddressBook.Enabled = False
-
- self.config.SudoersFile = ""
-
- if self.configOptions:
- self.config.update(self.configOptions)
-
- os.mkdir(self.config.ServerRoot)
- os.mkdir(os.path.join(self.config.ServerRoot, self.config.DocumentRoot))
- os.mkdir(os.path.join(self.config.ServerRoot, self.config.DataRoot))
- os.mkdir(os.path.join(self.config.ServerRoot, self.config.ConfigRoot))
-
- self.configFile = self.mktemp()
-
- self.writeConfig()
-
-
- def tearDown(self):
- config.setDefaults(DEFAULT_CONFIG)
- config.reset()
-
-
- def writeConfig(self):
- """
- Flush self.config out to self.configFile
- """
- writePlist(self.config, self.configFile)
-
-
- def makeService(self):
- """
- Create a service by calling into CalDAVServiceMaker with
- self.configFile
- """
- self.options.parseOptions(["-f", self.configFile])
-
- return CalDAVServiceMaker().makeService(self.options)
-
-
- def getSite(self):
- """
- Get the server.Site from the service by finding the HTTPFactory.
- """
- service = self.makeService()
- for listeningService in inServiceHierarchy(
- service,
- # FIXME: need a better predicate for 'is this really an HTTP
- # factory' but this works for now.
- # NOTE: in a database 'single' configuration, PostgresService
- # will prevent the HTTP services from actually getting added to
- # the hierarchy until the hierarchy has started.
- lambda x: hasattr(x, 'args')
- ):
- return listeningService.args[1].protocolArgs['requestFactory']
- raise RuntimeError("No site found.")
-
-
-
-def inServiceHierarchy(svc, predicate):
- """
- Find services in the service collection which satisfy the given predicate.
- """
- for subsvc in svc.services:
- if IServiceCollection.providedBy(subsvc):
- for value in inServiceHierarchy(subsvc, predicate):
- yield value
- if predicate(subsvc):
- yield subsvc
-
-
-
-def determineAppropriateGroupID():
- """
- Determine a secondary group ID which can be used for testing.
- """
- return os.getgroups()[1]
-
-
-
-class SocketGroupOwnership(TestCase):
- """
- Tests for L{GroupOwnedUNIXServer}.
- """
-
- def test_groupOwnedUNIXSocket(self):
- """
- When a L{GroupOwnedUNIXServer} is started, it will change the group of
- its socket.
- """
- alternateGroup = determineAppropriateGroupID()
- socketName = self.mktemp()
- gous = GroupOwnedUNIXServer(alternateGroup, socketName, ServerFactory(), mode=0660)
- gous.privilegedStartService()
- self.addCleanup(gous.stopService)
- filestat = os.stat(socketName)
- self.assertTrue(stat.S_ISSOCK(filestat.st_mode))
- self.assertEquals(filestat.st_gid, alternateGroup)
- self.assertEquals(filestat.st_uid, os.getuid())
-
-
-
-class CalDAVServiceMakerTests(BaseServiceMakerTests):
- """
- Test the service maker's behavior
- """
-
- def test_makeServiceDispatcher(self):
- """
- Test the default options of the dispatching makeService
- """
- validServices = ["Slave", "Combined"]
-
- self.config["HTTPPort"] = 0
-
- for service in validServices:
- self.config["ProcessType"] = service
- self.writeConfig()
- self.makeService()
-
- self.config["ProcessType"] = "Unknown Service"
- self.writeConfig()
- self.assertRaises(UsageError, self.makeService)
-
-
- def test_modesOnUNIXSockets(self):
- """
- The logging and stats UNIX sockets that are bound as part of the
- 'Combined' service hierarchy should have a secure mode specified: only
- the executing user should be able to open and send to them.
- """
-
- self.config["HTTPPort"] = 0 # Don't conflict with the test above.
- alternateGroup = determineAppropriateGroupID()
- self.config.GroupName = grp.getgrgid(alternateGroup).gr_name
-
- self.config["ProcessType"] = "Combined"
- self.writeConfig()
- svc = self.makeService()
- for serviceName in ["logging"]:
- socketService = svc.getServiceNamed(serviceName)
- self.assertIsInstance(socketService, GroupOwnedUNIXServer)
- m = socketService.kwargs.get("mode", 0666)
- self.assertEquals(
- m, int("660", 8),
- "Wrong mode on %s: %s" % (serviceName, oct(m))
- )
- self.assertEquals(socketService.gid, alternateGroup)
- for serviceName in ["stats"]:
- socketService = svc.getServiceNamed(serviceName)
- self.assertIsInstance(socketService, GroupOwnedUNIXServer)
- m = socketService.kwargs.get("mode", 0444)
- self.assertEquals(
- m, int("440", 8),
- "Wrong mode on %s: %s" % (serviceName, oct(m))
- )
- self.assertEquals(socketService.gid, alternateGroup)
-
-
- def test_processMonitor(self):
- """
- In the master, there should be exactly one
- L{DelayedStartupProcessMonitor} in the service hierarchy so that it
- will be started by startup.
- """
- self.config["ProcessType"] = "Combined"
- self.writeConfig()
- self.assertEquals(
- 1,
- len(
- list(inServiceHierarchy(
- self.makeService(),
- lambda x: isinstance(x, DelayedStartupProcessMonitor)))
- )
- )
-
-
-
-
-class SlaveServiceTest(BaseServiceMakerTests):
- """
- Test various configurations of the Slave service
- """
-
- configOptions = {
- "HTTPPort": 8008,
- "SSLPort": 8443,
- }
-
- def test_defaultService(self):
- """
- Test the value of a Slave service in it's simplest
- configuration.
- """
- service = self.makeService()
-
- self.failUnless(
- IService(service),
- "%s does not provide IService" % (service,)
- )
- self.failUnless(
- service.services,
- "No services configured"
- )
- self.failUnless(
- isinstance(service, CalDAVService),
- "%s is not a CalDAVService" % (service,)
- )
-
- def test_defaultListeners(self):
- """
- Test that the Slave service has sub services with the
- default TCP and SSL configuration
- """
- service = self.makeService()
-
- expectedSubServices = dict((
- (MaxAcceptTCPServer, self.config["HTTPPort"]),
- (MaxAcceptSSLServer, self.config["SSLPort"]),
- ))
-
- configuredSubServices = [(s.__class__, getattr(s, 'args', None))
- for s in service.services]
- checked = 0
- for serviceClass, serviceArgs in configuredSubServices:
- if serviceClass in expectedSubServices:
- checked += 1
- self.assertEquals(
- serviceArgs[0],
- dict(expectedSubServices)[serviceClass]
- )
- # TCP+SSL services for IPv4, TCP+SSL services for IPv6.
- self.assertEquals(checked, 4)
-
-
- def test_SSLKeyConfiguration(self):
- """
- Test that the configuration of the SSLServer reflect the config file's
- SSL Private Key and SSL Certificate
- """
- service = self.makeService()
-
- sslService = None
- for s in service.services:
- if isinstance(s, internet.SSLServer):
- sslService = s
- break
-
- self.failIf(sslService is None, "No SSL Service found")
-
- context = sslService.args[2]
-
- self.assertEquals(
- self.config["SSLPrivateKey"],
- context.privateKeyFileName
- )
- self.assertEquals(
- self.config["SSLCertificate"],
- context.certificateFileName,
- )
-
- def test_noSSL(self):
- """
- Test the single service to make sure there is no SSL Service when SSL
- is disabled
- """
- del self.config["SSLPort"]
- self.writeConfig()
-
- service = self.makeService()
-
- self.assertNotIn(
- internet.SSLServer,
- [s.__class__ for s in service.services]
- )
-
- def test_noHTTP(self):
- """
- Test the single service to make sure there is no TCPServer when
- HTTPPort is not configured
- """
- del self.config["HTTPPort"]
- self.writeConfig()
-
- service = self.makeService()
-
- self.assertNotIn(
- internet.TCPServer,
- [s.__class__ for s in service.services]
- )
-
- def test_singleBindAddresses(self):
- """
- Test that the TCPServer and SSLServers are bound to the proper address
- """
- self.config.BindAddresses = ["127.0.0.1"]
- self.writeConfig()
-
- service = self.makeService()
-
- for s in service.services:
- if isinstance(s, (internet.TCPServer, internet.SSLServer)):
- self.assertEquals(s.kwargs["interface"], "127.0.0.1")
-
- def test_multipleBindAddresses(self):
- """
- Test that the TCPServer and SSLServers are bound to the proper
- addresses.
- """
- self.config.BindAddresses = [
- "127.0.0.1",
- "10.0.0.2",
- "172.53.13.123",
- ]
-
- self.writeConfig()
- service = self.makeService()
-
- tcpServers = []
- sslServers = []
-
- for s in service.services:
- if isinstance(s, internet.TCPServer):
- tcpServers.append(s)
- elif isinstance(s, internet.SSLServer):
- sslServers.append(s)
-
- self.assertEquals(len(tcpServers), len(self.config.BindAddresses))
- self.assertEquals(len(sslServers), len(self.config.BindAddresses))
-
- for addr in self.config.BindAddresses:
- for s in tcpServers:
- if s.kwargs["interface"] == addr:
- tcpServers.remove(s)
-
- for s in sslServers:
- if s.kwargs["interface"] == addr:
- sslServers.remove(s)
-
- self.assertEquals(len(tcpServers), 0)
- self.assertEquals(len(sslServers), 0)
-
- def test_listenBacklog(self):
- """
- Test that the backlog arguments is set in TCPServer and SSLServers
- """
- self.config.ListenBacklog = 1024
- self.writeConfig()
- service = self.makeService()
-
- for s in service.services:
- if isinstance(s, (internet.TCPServer, internet.SSLServer)):
- self.assertEquals(s.kwargs["backlog"], 1024)
-
-
-class ServiceHTTPFactoryTests(BaseServiceMakerTests):
- """
- Test the configuration of the initial resource hierarchy of the
- single service
- """
- configOptions = {"HTTPPort": 8008}
-
- def test_AuthWrapperAllEnabled(self):
- """
- Test the configuration of the authentication wrapper
- when all schemes are enabled.
- """
- self.config.Authentication.Digest.Enabled = True
- self.config.Authentication.Kerberos.Enabled = True
- self.config.Authentication.Kerberos.ServicePrincipal = "http/hello at bob"
- self.config.Authentication.Basic.Enabled = True
-
- self.writeConfig()
- site = self.getSite()
-
- self.failUnless(isinstance(
- site.resource.resource,
- auth.AuthenticationWrapper))
-
- authWrapper = site.resource.resource
-
- expectedSchemes = ["negotiate", "digest", "basic"]
-
- for scheme in authWrapper.credentialFactories:
- self.failUnless(scheme in expectedSchemes)
-
- self.assertEquals(len(expectedSchemes),
- len(authWrapper.credentialFactories))
-
- def test_servicePrincipalNone(self):
- """
- Test that the Kerberos principal look is attempted if the principal is empty.
- """
- self.config.Authentication.Kerberos.ServicePrincipal = ""
- self.config.Authentication.Kerberos.Enabled = True
- self.writeConfig()
- site = self.getSite()
-
- authWrapper = site.resource.resource
-
- self.assertFalse(authWrapper.credentialFactories.has_key("negotiate"))
-
- def test_servicePrincipal(self):
- """
- Test that the kerberos realm is the realm portion of a principal
- in the form proto/host at realm
- """
- self.config.Authentication.Kerberos.ServicePrincipal = "http/hello at bob"
- self.config.Authentication.Kerberos.Enabled = True
- self.writeConfig()
- site = self.getSite()
-
- authWrapper = site.resource.resource
- ncf = authWrapper.credentialFactories["negotiate"]
-
- self.assertEquals(ncf.service, "http at HELLO")
- self.assertEquals(ncf.realm, "bob")
-
- def test_AuthWrapperPartialEnabled(self):
- """
- Test that the expected credential factories exist when
- only a partial set of authentication schemes is
- enabled.
- """
-
- self.config.Authentication.Basic.Enabled = False
- self.config.Authentication.Kerberos.Enabled = False
-
- self.writeConfig()
- site = self.getSite()
-
- authWrapper = site.resource.resource
-
- expectedSchemes = ["digest"]
-
- for scheme in authWrapper.credentialFactories:
- self.failUnless(scheme in expectedSchemes)
-
- self.assertEquals(
- len(expectedSchemes),
- len(authWrapper.credentialFactories)
- )
-
- def test_LogWrapper(self):
- """
- Test the configuration of the log wrapper
- """
- site = self.getSite()
-
- self.failUnless(isinstance(
- site.resource,
- LogWrapperResource))
-
- def test_rootResource(self):
- """
- Test the root resource
- """
- site = self.getSite()
- root = site.resource.resource.resource
-
- self.failUnless(isinstance(root, RootResource))
-
- def test_principalResource(self):
- """
- Test the principal resource
- """
- site = self.getSite()
- root = site.resource.resource.resource
-
- self.failUnless(isinstance(
- root.getChild("principals"),
- DirectoryPrincipalProvisioningResource
- ))
-
- def test_calendarResource(self):
- """
- Test the calendar resource
- """
- site = self.getSite()
- root = site.resource.resource.resource
-
- self.failUnless(isinstance(
- root.getChild("calendars"),
- DirectoryCalendarHomeProvisioningResource
- ))
-
-
-sudoersFile = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>users</key>
- <array>
- <dict>
- <key>password</key>
- <string>superuser</string>
- <key>username</key>
- <string>superuser</string>
- </dict>
- </array>
-</dict>
-</plist>
-"""
-
-class DirectoryServiceTest(BaseServiceMakerTests):
- """
- Tests of the directory service
- """
-
- configOptions = {"HTTPPort": 8008}
-
- def test_sameDirectory(self):
- """
- Test that the principal hierarchy has a reference
- to the same DirectoryService as the calendar hierarchy
- """
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- calendars = site.resource.resource.resource.getChild("calendars")
-
- self.assertEquals(principals.directory, calendars.directory)
-
- def test_aggregateDirectory(self):
- """
- Assert that the base directory service is actually
- an AggregateDirectoryService
- """
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- directory = principals.directory
-
- self.failUnless(isinstance(directory, AggregateDirectoryService))
-
- def test_sudoDirectoryService(self):
- """
- Test that a sudo directory service is available if the
- SudoersFile is set and exists
- """
- self.config.SudoersFile = "sudoers.plist"
- sudoersFilePath = os.path.join(
- self.config.ServerRoot,
- self.config.ConfigRoot,
- self.config.SudoersFile
- )
- self.writeConfig()
-
- open(sudoersFilePath, "w").write(sudoersFile)
-
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- directory = principals.directory
-
- self.failUnless(sudoersFilePath)
-
- sudoService = directory.serviceForRecordType(SudoDirectoryService.recordType_sudoers)
-
- self.assertEquals(
- sudoService.plistFile.path,
- os.path.abspath(sudoersFilePath)
- )
- self.failUnless(
- SudoDirectoryService.recordType_sudoers
- in directory.userRecordTypes
- )
-
- def test_sudoDirectoryServiceNoFile(self):
- """
- Test that there is no SudoDirectoryService if
- the SudoersFile does not exist.
- """
- self.config.SudoersFile = self.mktemp()
-
- self.writeConfig()
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- directory = principals.directory
-
- self.failUnless(self.config.SudoersFile)
-
- self.assertRaises(
- UnknownRecordTypeError,
- directory.serviceForRecordType,
- SudoDirectoryService.recordType_sudoers
- )
-
- def test_sudoDirectoryServiceNotConfigured(self):
- """
- Test that there is no SudoDirectoryService if
- the SudoersFile is not configured
- """
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- directory = principals.directory
-
- self.failIf(self.config.SudoersFile)
-
- self.assertRaises(
- UnknownRecordTypeError,
- directory.serviceForRecordType,
- SudoDirectoryService.recordType_sudoers
- )
-
- def test_configuredDirectoryService(self):
- """
- Test that the real directory service is the directory service
- set in the configuration file.
- """
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- directory = principals.directory
-
- realDirectory = directory.serviceForRecordType("users")
-
- configuredDirectory = namedAny(self.config.DirectoryService.type)
-
- self.failUnless(isinstance(realDirectory, configuredDirectory))
-
-
-
-class DummyProcessObject(object):
- """
- Simple stub for Process Object API which just has an executable and some
- arguments.
-
- This is a stand in for L{TwistdSlaveProcess}.
- """
-
- def __init__(self, scriptname, *args):
- self.scriptname = scriptname
- self.args = args
-
-
- def getCommandLine(self):
- """
- Simple command line.
- """
- return [self.scriptname] + list(self.args)
-
-
- def getFileDescriptors(self):
- """
- Return a dummy, empty mapping of file descriptors.
- """
- return {}
-
-
- def getName(self):
- """
- Get a dummy name.
- """
- return 'Dummy'
-
-
-class ScriptProcessObject(DummyProcessObject):
- """
- Simple stub for the Process Object API that will run a test script.
- """
-
- def getCommandLine(self):
- """
- Get the command line to invoke this script.
- """
- return [
- sys.executable,
- FilePath(__file__).sibling(self.scriptname).path
- ] + list(self.args)
-
-
-
-
-
-class DelayedStartupProcessMonitorTests(TestCase):
- """
- Test cases for L{DelayedStartupProcessMonitor}.
- """
-
- def test_lineAfterLongLine(self):
- """
- A "long" line of output from a monitored process (longer than
- L{LineReceiver.MAX_LENGTH}) should be logged in chunks rather than all
- at once, to avoid resource exhaustion.
- """
- dspm = DelayedStartupProcessMonitor()
- dspm.addProcessObject(ScriptProcessObject(
- 'longlines.py', str(DelayedStartupLineLogger.MAX_LENGTH)),
- os.environ)
- dspm.startService()
- self.addCleanup(dspm.stopService)
-
- logged = []
-
- def tempObserver(event):
- # Probably won't be a problem, but let's not have any intermittent
- # test issues that stem from multi-threaded log messages randomly
- # going off...
- if not isInIOThread():
- callFromThread(tempObserver, event)
- return
- if event.get('isError'):
- d.errback()
- m = event.get('message')[0]
- if m.startswith('[Dummy] '):
- logged.append(event)
- if m == '[Dummy] z':
- d.callback("done")
-
- log.addObserver(tempObserver)
- self.addCleanup(log.removeObserver, tempObserver)
- d = Deferred()
- def assertions(result):
- self.assertEquals(["[Dummy] x",
- "[Dummy] y",
- "[Dummy] y", # final segment
- "[Dummy] z"],
- [''.join(evt['message'])[:len('[Dummy]') + 2]
- for evt in logged])
- self.assertEquals([" (truncated, continued)",
- " (truncated, continued)",
- "[Dummy] y",
- "[Dummy] z"],
- [''.join(evt['message'])[-len(" (truncated, continued)"):]
- for evt in logged])
- d.addCallback(assertions)
- return d
-
-
- def test_breakLineIntoSegments(self):
- """
- Exercise the line-breaking logic with various key lengths
- """
- testLogger = DelayedStartupLineLogger()
- testLogger.MAX_LENGTH = 10
- for input, output in [
- ("", []),
- ("a", ["a"]),
- ("abcde", ["abcde"]),
- ("abcdefghij", ["abcdefghij"]),
- ("abcdefghijk",
- ["abcdefghij (truncated, continued)",
- "k"
- ]
- ),
- ("abcdefghijklmnopqrst",
- ["abcdefghij (truncated, continued)",
- "klmnopqrst"
- ]
- ),
- ("abcdefghijklmnopqrstuv",
- ["abcdefghij (truncated, continued)",
- "klmnopqrst (truncated, continued)",
- "uv"]
- ),
- ]:
- self.assertEquals(output, testLogger._breakLineIntoSegments(input))
-
-
- def test_acceptDescriptorInheritance(self):
- """
- If a L{TwistdSlaveProcess} specifies some file descriptors to be
- inherited, they should be inherited by the subprocess.
- """
- imps = InMemoryProcessSpawner()
- dspm = DelayedStartupProcessMonitor(imps)
-
- # Most arguments here will be ignored, so these are bogus values.
- slave = TwistdSlaveProcess(
- twistd = "bleh",
- tapname = "caldav",
- configFile = "/does/not/exist",
- id = 10,
- interfaces = '127.0.0.1',
- inheritFDs = [3, 7],
- inheritSSLFDs = [19, 25],
- )
-
- dspm.addProcessObject(slave, {})
- dspm.startService()
- # We can easily stub out spawnProcess, because caldav calls it, but a
- # bunch of callLater calls are buried in procmon itself, so we need to
- # use the real clock.
- oneProcessTransport = imps.waitForOneProcess()
- self.assertEquals(oneProcessTransport.childFDs,
- {0: 'w', 1: 'r', 2: 'r',
- 3: 3, 7: 7,
- 19: 19, 25: 25})
-
-
- def test_changedArgumentEachSpawn(self):
- """
- If the result of C{getCommandLine} changes on subsequent calls,
- subsequent calls should result in different arguments being passed to
- C{spawnProcess} each time.
- """
- imps = InMemoryProcessSpawner()
- dspm = DelayedStartupProcessMonitor(imps)
- slave = DummyProcessObject('scriptname', 'first')
- dspm.addProcessObject(slave, {})
- dspm.startService()
- oneProcessTransport = imps.waitForOneProcess()
- self.assertEquals(oneProcessTransport.args,
- ['scriptname', 'first'])
- slave.args = ['second']
- oneProcessTransport.processProtocol.processEnded(None)
- twoProcessTransport = imps.waitForOneProcess()
- self.assertEquals(twoProcessTransport.args,
- ['scriptname', 'second'])
-
-
- def test_metaDescriptorInheritance(self):
- """
- If a L{TwistdSlaveProcess} specifies a meta-file-descriptor to be
- inherited, it should be inherited by the subprocess, and a
- configuration argument should be passed that indicates to the
- subprocess.
- """
- imps = InMemoryProcessSpawner()
- dspm = DelayedStartupProcessMonitor(imps)
- # Most arguments here will be ignored, so these are bogus values.
- slave = TwistdSlaveProcess(
- twistd = "bleh",
- tapname = "caldav",
- configFile = "/does/not/exist",
- id = 10,
- interfaces = '127.0.0.1',
- metaSocket = FakeDispatcher().addSocket()
- )
-
- dspm.addProcessObject(slave, {})
- dspm.startService()
- oneProcessTransport = imps.waitForOneProcess()
- self.assertIn("MetaFD=4", oneProcessTransport.args)
- self.assertEquals(
- oneProcessTransport.args[oneProcessTransport.args.index("MetaFD=4")-1],
- '-o',
- "MetaFD argument was not passed as an option"
- )
- self.assertEquals(oneProcessTransport.childFDs,
- {0: 'w', 1: 'r', 2: 'r',
- 4: 4})
-
-
- def test_startServiceDelay(self):
- """
- Starting a L{DelayedStartupProcessMonitor} should result in the process
- objects that have been added to it being started once per
- delayInterval.
- """
- imps = InMemoryProcessSpawner()
- dspm = DelayedStartupProcessMonitor(imps)
- dspm.delayInterval = 3.0
- sampleCounter = range(0, 5)
- for counter in sampleCounter:
- slave = TwistdSlaveProcess(
- twistd = "bleh",
- tapname = "caldav",
- configFile = "/does/not/exist",
- id = counter * 10,
- interfaces = '127.0.0.1',
- metaSocket = FakeDispatcher().addSocket()
- )
- dspm.addProcessObject(slave, {"SAMPLE_ENV_COUNTER": str(counter)})
- dspm.startService()
-
- # Advance the clock a bunch of times, allowing us to time things with a
- # comprehensible resolution.
- imps.pump([0] + [dspm.delayInterval / 2.0] * len(sampleCounter) * 3)
- expectedValues = [dspm.delayInterval * n for n in sampleCounter]
- self.assertEquals([x.startedAt for x in imps.processTransports],
- expectedValues)
-
-
-
-class FakeFD(object):
-
- def __init__(self, n):
- self.fd = n
-
-
- def fileno(self):
- return self.fd
-
-
-
-class FakeDispatcher(object):
- n = 3
-
- def addSocket(self):
- self.n += 1
- return FakeFD(self.n)
-
-
-
-class TwistdSlaveProcessTests(TestCase):
- """
- Tests for L{TwistdSlaveProcess}.
- """
- def test_pidfile(self):
- """
- The result of L{TwistdSlaveProcess.getCommandLine} includes an option
- setting the name of the pidfile to something including the instance id.
- """
- slave = TwistdSlaveProcess("/path/to/twistd", "something", "config", 7, [])
- commandLine = slave.getCommandLine()
-
- option = 'PIDFile=something-instance-7.pid'
- self.assertIn(option, commandLine)
- self.assertEquals(commandLine[commandLine.index(option) - 1], '-o')
-
Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/calendarserver/tap/util.py 2012-03-30 22:08:34 UTC (rev 8955)
@@ -53,7 +53,6 @@
from twistedcaldav.directory.directory import GroupMembershipCache
from twistedcaldav.directory.internal import InternalDirectoryService
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.sudo import SudoDirectoryService
from twistedcaldav.directory.wiki import WikiDirectoryService
from twistedcaldav.notify import NotifierFactory, getPubSubConfiguration
from calendarserver.push.applepush import APNSubscriptionResource
@@ -312,25 +311,6 @@
directories.append(resourceDirectory)
#
- # Add sudoers directory
- #
- sudoDirectory = None
-
- if config.SudoersFile and os.path.exists(config.SudoersFile):
- log.info("Configuring SudoDirectoryService with file: %s"
- % (config.SudoersFile,))
-
- sudoDirectory = SudoDirectoryService(config.SudoersFile)
- sudoDirectory.realmName = baseDirectory.realmName
-
- CalDAVResource.sudoDirectory = sudoDirectory
- directories.insert(0, sudoDirectory)
- else:
- log.info( "Not using SudoDirectoryService; file doesn't exist: %s"
- % (config.SudoersFile,)
- )
-
- #
# Add wiki directory service
#
if config.Authentication.Wiki.Enabled:
@@ -348,10 +328,6 @@
directory = AggregateDirectoryService(directories, groupMembershipCache)
- if sudoDirectory:
- directory.userRecordTypes.insert(0,
- SudoDirectoryService.recordType_sudoers)
-
#
# Use system-wide realm on OSX
#
Modified: CalendarServer/trunk/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/resource.py 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/twext/web2/dav/resource.py 2012-03-30 22:08:34 UTC (rev 8955)
@@ -948,51 +948,62 @@
if the authentication scheme is unsupported, or the
credentials provided by the request are not valid.
"""
- if not (hasattr(request, 'portal') and
- hasattr(request, 'credentialFactories') and
- hasattr(request, 'loginInterfaces')):
+ # Bypass normal authentication if its already been done (by SACL check)
+ if (
+ hasattr(request, "authnUser") and
+ hasattr(request, "authzUser") and
+ request.authnUser is not None and
+ request.authzUser is not None
+ ):
+ return succeed((request.authnUser, request.authzUser))
+
+ if not (
+ hasattr(request, "portal") and
+ hasattr(request, "credentialFactories") and
+ hasattr(request, "loginInterfaces")
+ ):
request.authnUser = element.Principal(element.Unauthenticated())
request.authzUser = element.Principal(element.Unauthenticated())
return succeed((request.authnUser, request.authzUser))
- authHeader = request.headers.getHeader('authorization')
+ authHeader = request.headers.getHeader("authorization")
if authHeader is not None:
if authHeader[0] not in request.credentialFactories:
- log.err("Client authentication scheme %s is not "
- "provided by server %s"
- % (authHeader[0], request.credentialFactories.keys()))
+ log.err(
+ "Client authentication scheme %s is not provided by server %s"
+ % (authHeader[0], request.credentialFactories.keys())
+ )
d = UnauthorizedResponse.makeResponse(
request.credentialFactories,
request.remoteAddr
)
- def _fail(response):
- return Failure(HTTPError(response))
- return d.addCallback(_fail)
+ return d.addCallback(lambda response: Failure(HTTPError(response)))
else:
factory = request.credentialFactories[authHeader[0]]
def gotCreds(creds):
- return self.principalsForAuthID(
- request, creds.username
- ).addCallback(gotDetails, creds)
+ d = self.principalsForAuthID(request, creds.username)
+ d.addCallback(gotDetails, creds)
+ return d
# Try to match principals in each principal collection
# on the resource
def gotDetails(details, creds):
if details == (None, None):
- log.msg("Could not find the principal resource for user id: %s" % (creds.username,))
- raise HTTPError(responsecode.UNAUTHORIZED)
+ log.msg(
+ "Could not find the principal resource for user id: %s"
+ % (creds.username,)
+ )
+ return Failure(HTTPError(responsecode.UNAUTHORIZED))
authnPrincipal = IDAVPrincipalResource(details[0])
authzPrincipal = IDAVPrincipalResource(details[1])
- return PrincipalCredentials(
- authnPrincipal, authzPrincipal, creds
- )
+ return PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
def login(pcreds):
- return request.portal.login(
- pcreds, None, *request.loginInterfaces)
+ return request.portal.login(pcreds, None, *request.loginInterfaces)
+
def gotAuth(result):
request.authnUser = result[1]
request.authzUser = result[2]
@@ -1004,9 +1015,7 @@
d = UnauthorizedResponse.makeResponse(
request.credentialFactories, request.remoteAddr
)
- d.addCallback(
- lambda response: Failure(HTTPError(response))
- )
+ d.addCallback(lambda response: Failure(HTTPError(response)))
return d
d = factory.decode(authHeader[1], request)
Deleted: CalendarServer/trunk/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/sudo.py 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/twistedcaldav/directory/sudo.py 2012-03-30 22:08:34 UTC (rev 8955)
@@ -1,144 +0,0 @@
-##
-# Copyright (c) 2006-2012 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.
-##
-
-"""
-Directory service implementation for users who are allowed to authorize
-as other principals.
-"""
-
-__all__ = [
- "SudoDirectoryService",
-]
-
-from twext.python.filepath import CachingFilePath as FilePath
-from twisted.cred.credentials import IUsernamePassword, IUsernameHashedPassword
-from twisted.cred.error import UnauthorizedLogin
-
-from twext.python.plistlib import readPlist
-
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-
-class SudoDirectoryService(DirectoryService):
- """
- L{IDirectoryService} implementation for Sudo users.
- """
- baseGUID = "1EE00E46-1885-4DBC-A001-590AFA76A8E3"
-
- realmName = None
-
- plistFile = None
-
- recordType_sudoers = "sudoers"
-
- def __repr__(self):
- return "<%s %r: %r>" % (self.__class__.__name__, self.realmName,
- self.plistFile)
-
- def __init__(self, plistFile):
- super(SudoDirectoryService, self).__init__()
-
- if isinstance(plistFile, (unicode, str)):
- plistFile = FilePath(plistFile)
-
- self.plistFile = plistFile
- self._fileInfo = None
- self._plist = None
- self._accounts()
-
- def _accounts(self):
- if self._plist is None:
- self.plistFile.restat()
- fileInfo = (self.plistFile.getmtime(), self.plistFile.getsize())
- if fileInfo != self._fileInfo:
- self._plist = readPlist(self.plistFile.path)
- self._fileInfo = fileInfo
-
- return self._plist
-
- def recordTypes(self):
- return (SudoDirectoryService.recordType_sudoers,)
-
- def _recordForEntry(self, entry):
- return SudoDirectoryRecord(
- service=self,
- recordType=SudoDirectoryService.recordType_sudoers,
- shortName=entry['username'],
- entry=entry)
-
-
- def listRecords(self, recordType):
- if recordType != SudoDirectoryService.recordType_sudoers:
- raise UnknownRecordTypeError(recordType)
-
- for entry in self._accounts()['users']:
- yield self._recordForEntry(entry)
-
- def recordWithShortName(self, recordType, shortName):
- if recordType != SudoDirectoryService.recordType_sudoers:
- raise UnknownRecordTypeError(recordType)
-
- for entry in self._accounts()['users']:
- if entry['username'] == shortName:
- return self._recordForEntry(entry)
-
- def requestAvatarId(self, credentials):
- # FIXME: ?
- # We were checking if principal is enabled; seems unnecessary in current
- # implementation because you shouldn't have a principal object for a
- # disabled directory principal.
-
- if credentials.authnPrincipal is None or not hasattr(credentials.authnPrincipal, "record"):
- raise UnauthorizedLogin("No such user: %s" % (credentials.credentials.username,))
- sudouser = credentials.authnPrincipal.record
-
- if credentials.authnPrincipal.record.verifyCredentials(credentials.credentials):
- return (
- credentials.authnPrincipal.principalURL(),
- credentials.authzPrincipal.principalURL(),
- credentials.authnPrincipal,
- credentials.authzPrincipal,
- )
- else:
- raise UnauthorizedLogin(
- "Incorrect credentials for %s" % (sudouser,))
-
-
-class SudoDirectoryRecord(DirectoryRecord):
- """
- L{DirectoryRecord} implementation for Sudo users.
- """
-
- def __init__(self, service, recordType, shortName, entry):
- super(SudoDirectoryRecord, self).__init__(
- service=service,
- recordType=recordType,
- uid="%s:%s" % (recordType, shortName),
- shortNames=(shortName,),
- fullName=shortName,
- )
-
- self.password = entry['password']
-
- self.enabled = True # Explicitly enabled
-
- def verifyCredentials(self, credentials):
- if IUsernamePassword.providedBy(credentials):
- return credentials.checkPassword(self.password)
- elif IUsernameHashedPassword.providedBy(credentials):
- return credentials.checkPassword(self.password)
-
- return super(SudoDirectoryRecord, self).verifyCredentials(credentials)
Modified: CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist 2012-03-30 22:08:34 UTC (rev 8955)
@@ -284,10 +284,6 @@
<!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
</array>
- <!-- Principals that can pose as other principals -->
- <key>SudoersFile</key>
- <string>conf/sudoers.plist</string>
-
<!-- Create "proxy access" principals -->
<key>EnableProxyPrincipals</key>
<true/>
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_sudo.py 2012-03-30 22:08:34 UTC (rev 8955)
@@ -1,75 +0,0 @@
-##
-# Copyright (c) 2005-2009 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 twext.python.filepath import CachingFilePath as FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.sudo import SudoDirectoryService
-
-plistFile = FilePath(os.path.join(os.path.dirname(__file__), "sudoers.plist"))
-plistFile2 = FilePath(os.path.join(os.path.dirname(__file__), "sudoers2.plist"))
-
-class SudoTestCase(
- twistedcaldav.directory.test.util.BasicTestCase,
- twistedcaldav.directory.test.util.DigestTestCase
-):
- """
- Test the Sudo Directory Service
- """
-
- recordTypes = set(('sudoers',))
- recordType = 'sudoers'
-
- sudoers = {
- 'alice': {'password': 'alice',},
- }
-
- locations = {}
-
- def plistFile(self):
- if not hasattr(self, "_plistFile"):
- self._plistFile = FilePath(self.mktemp())
- plistFile.copyTo(self._plistFile)
- return self._plistFile
-
- def service(self):
- service = SudoDirectoryService(self.plistFile())
- service.realmName = "test realm"
- return service
-
- def test_listRecords(self):
- for record in self.service().listRecords(self.recordType):
- self.failUnless(record.shortNames[0] in self.sudoers)
- self.assertEqual(self.sudoers[record.shortNames[0]]['password'],
- record.password)
-
- def test_recordWithShortName(self):
- service = self.service()
-
- record = service.recordWithShortName(self.recordType, 'alice')
- self.assertEquals(record.password, 'alice')
-
- record = service.recordWithShortName(self.recordType, 'bob')
- self.failIf(record)
-
- def test_calendaringDisabled(self):
- service = self.service()
-
- record = service.recordWithShortName(self.recordType, 'alice')
-
- self.failIf(record.enabledForCalendaring,
- "sudoers should have enabledForCalendaring=False")
Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/twistedcaldav/extensions.py 2012-03-30 22:08:34 UTC (rev 8955)
@@ -65,7 +65,6 @@
from twistedcaldav import customxml
from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.directory.sudo import SudoDirectoryService
from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.method.report import http_REPORT
@@ -77,208 +76,6 @@
log = Logger()
-class SudoersMixin (object):
- """
- Mixin class to let DAVResource, and DAVFile subclasses know about
- sudoer principals and how to find their AuthID.
- """
-
- @inlineCallbacks
- def authenticate(self, request):
- # Bypass normal authentication if its already been done (by SACL check)
- if (
- hasattr(request, "authnUser") and
- hasattr(request, "authzUser") and
- request.authnUser is not None and
- request.authzUser is not None
- ):
- returnValue((request.authnUser, request.authzUser))
-
- # Copy of SuperDAVResource.authenticate except we pass the
- # creds on as well as we will need to take different actions
- # based on what the auth method was
- if not (
- hasattr(request, "portal") and
- hasattr(request, "credentialFactories") and
- hasattr(request, "loginInterfaces")
- ):
- request.authnUser = element.Principal(element.Unauthenticated())
- request.authzUser = element.Principal(element.Unauthenticated())
- returnValue((request.authnUser, request.authzUser,))
-
- authHeader = request.headers.getHeader("authorization")
-
- if authHeader is not None:
- if authHeader[0] not in request.credentialFactories:
- log.error("Client authentication scheme %s is not provided by server %s"
- % (authHeader[0], request.credentialFactories.keys()))
-
- response = (yield UnauthorizedResponse.makeResponse(
- request.credentialFactories,
- request.remoteAddr
- ))
- raise HTTPError(response)
- else:
- factory = request.credentialFactories[authHeader[0]]
-
- try:
- creds = (yield factory.decode(authHeader[1], request))
- except (UnauthorizedLogin, LoginFailed,):
- raise HTTPError((yield UnauthorizedResponse.makeResponse(
- request.credentialFactories, request.remoteAddr)))
-
- # Try to match principals in each principal collection on the resource
- authnPrincipal, authzPrincipal = (yield self.principalsForAuthID(request, creds))
- if (authnPrincipal, authzPrincipal) == (None, None):
- log.info("Could not find the principal resource for user id: %s" % (creds.username,))
- raise HTTPError(responsecode.UNAUTHORIZED)
-
- authnPrincipal = IDAVPrincipalResource(authnPrincipal)
- authzPrincipal = IDAVPrincipalResource(authzPrincipal)
-
- pcreds = PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
-
- try:
- result = (yield request.portal.login(pcreds, None, *request.loginInterfaces))
- except UnauthorizedLogin:
- raise HTTPError((yield UnauthorizedResponse.makeResponse(
- request.credentialFactories, request.remoteAddr)))
- request.authnUser = result[1]
- request.authzUser = result[2]
- returnValue((request.authnUser, request.authzUser,))
- else:
- if (
- hasattr(request, "checkedWiki") and
- hasattr(request, "authnUser") and
- hasattr(request, "authzUser")
- ):
- # This request has already been authenticated via the wiki
- returnValue((request.authnUser, request.authzUser))
-
- request.authnUser = element.Principal(element.Unauthenticated())
- request.authzUser = element.Principal(element.Unauthenticated())
- returnValue((request.authnUser, request.authzUser,))
-
- def principalsForAuthID(self, request, creds):
- """
- Return authentication and authorization prinicipal identifiers
- for the authentication identifer passed in. In this
- implementation authn and authz principals are the same.
-
- @param request: the L{IRequest} for the request in progress.
- @param creds: L{Credentials} or the principal to lookup.
- @return: a deferred tuple of two tuples. Each tuple is
- C{(principal, principalURI)} where: C{principal} is the
- L{Principal} that is found; {principalURI} is the C{str}
- URI of the principal. The first tuple corresponds to
- authentication identifiers, the second to authorization
- identifiers. It will errback with an
- HTTPError(responsecode.FORBIDDEN) if the principal isn't
- found.
- """
- authnPrincipal = self.findPrincipalForAuthID(creds)
-
- if authnPrincipal is None:
- return succeed((None, None))
-
- d = self.authorizationPrincipal(request, creds.username, authnPrincipal)
- d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
- return d
-
- def findPrincipalForAuthID(self, creds):
- """
- Return an authentication and authorization principal
- identifiers for the authentication identifier passed in.
- Check for sudo users before regular users.
- """
- if type(creds) is str:
- return super(SudoersMixin, self).findPrincipalForAuthID(creds)
-
- for collection in self.principalCollections():
- principal = collection.principalForShortName(
- SudoDirectoryService.recordType_sudoers,
- creds.username)
- if principal is not None:
- return principal
-
- for collection in self.principalCollections():
- principal = collection.principalForAuthID(creds)
- if principal is not None:
- return principal
- return None
-
- @inlineCallbacks
- def authorizationPrincipal(self, request, authID, authnPrincipal):
- """
- Determine the authorization principal for the given request
- and authentication principal. This implementation looks for
- an X-Authorize-As header value to use as the authorization
- principal.
-
- @param request: the L{IRequest} for the request in progress.
- @param authID: a string containing the
- authentication/authorization identifier for the principal
- to lookup.
- @param authnPrincipal: the L{IDAVPrincipal} for the
- authenticated principal
- @return: a deferred result C{tuple} of (L{IDAVPrincipal},
- C{str}) containing the authorization principal resource
- and URI respectively.
- """
- # Look for X-Authorize-As Header
- authz = request.headers.getRawHeaders("x-authorize-as")
-
- if authz is not None and (len(authz) == 1):
- # Substitute the authz value for principal look up
- authz = authz[0]
-
- def getPrincipalForType(type, name):
- for collection in self.principalCollections():
- principal = collection.principalForShortName(type, name)
- if principal:
- return principal
-
- def isSudoUser(authzID):
- if getPrincipalForType(SudoDirectoryService.recordType_sudoers, authzID):
- return True
- return False
-
- if (
- hasattr(authnPrincipal, "record") and
- authnPrincipal.record.recordType == SudoDirectoryService.recordType_sudoers
- ):
- if authz:
- if isSudoUser(authz):
- log.info("Cannot proxy as another proxy: user %r as user %r"
- % (authID, authz))
- raise HTTPError(responsecode.FORBIDDEN)
- else:
- authzPrincipal = getPrincipalForType(DirectoryService.recordType_users, authz)
-
- if not authzPrincipal:
- authzPrincipal = self.findPrincipalForAuthID(authz)
-
- if authzPrincipal is not None:
- log.info("Allow proxy: user %r as %r"
- % (authID, authz,))
- returnValue(authzPrincipal)
- else:
- log.info("Could not find authorization user id: %r"
- % (authz,))
- raise HTTPError(responsecode.FORBIDDEN)
- else:
- log.info("Cannot authenticate proxy user %r without X-Authorize-As header"
- % (authID,))
- raise HTTPError(responsecode.BAD_REQUEST)
- elif authz:
- log.info("Cannot proxy: user %r as %r" % (authID, authz,))
- raise HTTPError(responsecode.FORBIDDEN)
- else:
- # No proxy - do default behavior
- result = (yield super(SudoersMixin, self).authorizationPrincipal(request, authID, authnPrincipal))
- returnValue(result)
-
-
class DirectoryPrincipalPropertySearchMixIn(object):
@inlineCallbacks
@@ -673,7 +470,7 @@
return wrapper
class DAVResource (DirectoryPrincipalPropertySearchMixIn,
- SudoersMixin, SuperDAVResource, LoggingMixIn,
+ SuperDAVResource, LoggingMixIn,
DirectoryRenderingMixIn, StaticRenderMixin):
"""
Extended L{twext.web2.dav.resource.DAVResource} implementation.
@@ -888,8 +685,7 @@
-class DAVFile (SudoersMixin, SuperDAVFile, LoggingMixIn,
- DirectoryRenderingMixIn):
+class DAVFile (SuperDAVFile, LoggingMixIn, DirectoryRenderingMixIn):
"""
Extended L{twext.web2.dav.static.DAVFile} implementation.
"""
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2012-03-30 21:12:58 UTC (rev 8954)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2012-03-30 22:08:34 UTC (rev 8955)
@@ -384,7 +384,6 @@
#
"AdminPrincipals": [], # Principals with "DAV:all" access (relative URLs)
"ReadPrincipals": [], # Principals with "DAV:read" access (relative URLs)
- "SudoersFile": "sudoers.plist", # Principals that can pose as other principals
"EnableProxyPrincipals": True, # Create "proxy access" principals
#
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120330/291e6870/attachment-0001.html>
More information about the calendarserver-changes
mailing list