[CalendarServer-changes] [3402] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Thu Nov 20 10:43:50 PST 2008
Revision: 3402
http://trac.macosforge.org/projects/calendarserver/changeset/3402
Author: wsanchez at apple.com
Date: 2008-11-20 10:43:50 -0800 (Thu, 20 Nov 2008)
Log Message:
-----------
Move twistedcaldav.tap to calendarserver.tap.caldav.
Modified Paths:
--------------
CalendarServer/trunk/test
CalendarServer/trunk/twisted/plugins/caldav.py
CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py
Added Paths:
-----------
CalendarServer/trunk/calendarserver/tap/
CalendarServer/trunk/calendarserver/tap/__init__.py
CalendarServer/trunk/calendarserver/tap/caldav.py
CalendarServer/trunk/calendarserver/test/
CalendarServer/trunk/calendarserver/test/__init__.py
CalendarServer/trunk/calendarserver/test/test_tap.py
Removed Paths:
-------------
CalendarServer/trunk/twistedcaldav/tap.py
CalendarServer/trunk/twistedcaldav/test/test_tap.py
Copied: CalendarServer/trunk/calendarserver/tap/caldav.py (from rev 3399, CalendarServer/trunk/twistedcaldav/tap.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py 2008-11-20 18:43:50 UTC (rev 3402)
@@ -0,0 +1,759 @@
+##
+# Copyright (c) 2005-2008 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 subprocess import Popen, PIPE
+from pwd import getpwnam
+from grp import getgrnam
+from OpenSSL import SSL
+
+from zope.interface import implements
+
+from twisted.internet.ssl import DefaultOpenSSLContextFactory
+from twisted.internet.address import IPv4Address
+from twisted.python.log import FileLogObserver
+from twisted.python.usage import Options, UsageError
+from twisted.python.reflect import namedClass
+from twisted.application import internet, service
+from twisted.plugin import IPlugin
+from twisted.scripts.mktap import getid
+from twisted.cred.portal import Portal
+from twisted.web2.dav import auth
+from twisted.web2.auth.basic import BasicCredentialFactory
+from twisted.web2.server import Site
+
+from twistedcaldav.log import Logger, logLevelForNamespace, setLogLevelForNamespace
+from twistedcaldav.accesslog import DirectoryLogWrapperResource
+from twistedcaldav.accesslog import RotatingFileAccessLoggingObserver
+from twistedcaldav.accesslog import AMPCommonAccessLoggingObserver
+from twistedcaldav.cluster import makeService_Combined, makeService_Master
+from twistedcaldav.config import config, parseConfig, defaultConfig, defaultConfigFile, ConfigurationError
+from twistedcaldav.root import RootResource
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.directory.digest import QopDigestCredentialFactory
+from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+from twistedcaldav.directory.aggregate import AggregateDirectoryService
+from twistedcaldav.directory.sudo import SudoDirectoryService
+from twistedcaldav.directory.wiki import WikiDirectoryService
+from twistedcaldav.httpfactory import HTTP503LoggingFactory
+from twistedcaldav.static import CalendarHomeProvisioningFile
+from twistedcaldav.static import IScheduleInboxFile
+from twistedcaldav.static import TimezoneServiceFile
+from twistedcaldav.mail import IMIPInboxResource
+from twistedcaldav.timezones import TimezoneCache
+from twistedcaldav.upgrade import UpgradeTheServer
+from twistedcaldav import pdmonster
+from twistedcaldav import memcachepool
+from twistedcaldav.notify import installNotificationClient
+
+log = Logger()
+
+try:
+ from twistedcaldav.authkerb import NegotiateCredentialFactory
+except ImportError:
+ NegotiateCredentialFactory = None
+
+
+class CalDAVService (service.MultiService):
+ def __init__(self, logObserver):
+ self.logObserver = logObserver
+ service.MultiService.__init__(self)
+
+ def privilegedStartService(self):
+ service.MultiService.privilegedStartService(self)
+ self.logObserver.start()
+
+ def stopService(self):
+ service.MultiService.stopService(self)
+ self.logObserver.stop()
+
+
+class CalDAVOptions (Options):
+ optParameters = [[
+ "config", "f", defaultConfigFile, "Path to configuration file."
+ ]]
+
+ zsh_actions = {"config" : "_files -g '*.plist'"}
+
+ def __init__(self, *args, **kwargs):
+ super(CalDAVOptions, self).__init__(*args, **kwargs)
+
+ self.overrides = {}
+
+ @staticmethod
+ def coerceOption(configDict, key, value):
+ """
+ Coerce the given C{val} to type of C{configDict[key]}
+ """
+ if key in configDict:
+ if isinstance(configDict[key], bool):
+ value = value == "True"
+
+ elif isinstance(configDict[key], (int, float, long)):
+ value = type(configDict[key])(value)
+
+ elif isinstance(configDict[key], (list, tuple)):
+ value = value.split(",")
+
+ elif isinstance(configDict[key], dict):
+ raise UsageError(
+ "Dict options not supported on the command line"
+ )
+
+ elif value == "None":
+ value = None
+
+ return value
+
+ @classmethod
+ def setOverride(cls, configDict, path, value, overrideDict):
+ """
+ Set the value at path in configDict
+ """
+ key = path[0]
+
+ if len(path) == 1:
+ overrideDict[key] = cls.coerceOption(configDict, key, value)
+ return
+
+ if key in configDict:
+ if not isinstance(configDict[key], dict):
+ raise UsageError(
+ "Found intermediate path element that is not a dictionary"
+ )
+
+ if key not in overrideDict:
+ overrideDict[key] = {}
+
+ cls.setOverride(
+ configDict[key], path[1:],
+ value, overrideDict[key]
+ )
+
+ def opt_option(self, option):
+ """
+ Set an option to override a value in the config file. True, False, int,
+ and float options are supported, as well as comma seperated lists. Only
+ one option may be given for each --option flag, however multiple
+ --option flags may be specified.
+ """
+ if "=" in option:
+ path, value = option.split("=")
+ self.setOverride(
+ defaultConfig,
+ path.split("/"),
+ value,
+ self.overrides
+ )
+ else:
+ self.opt_option("%s=True" % (option,))
+
+ opt_o = opt_option
+
+ def postOptions(self):
+ if not os.path.exists(self["config"]):
+ log.info("Config file %s not found, using defaults"
+ % (self["config"],))
+
+ log.info("Reading configuration from file: %s" % (self["config"],))
+ parseConfig(self["config"])
+
+ config.updateDefaults(self.overrides)
+
+ uid, gid = None, None
+
+ if self.parent["uid"] or self.parent["gid"]:
+ uid, gid = getid(self.parent["uid"], self.parent["gid"])
+
+ def gottaBeRoot():
+ if os.getuid() != 0:
+ import pwd
+ username = pwd.getpwuid(os.getuid())[0]
+ raise UsageError("Only root can drop privileges. You are: %r"
+ % (username,))
+
+ if uid and uid != os.getuid():
+ gottaBeRoot()
+
+ if gid and gid != os.getgid():
+ gottaBeRoot()
+
+ #
+ # Ignore the logfile parameter if not daemonized and log to stdout.
+ #
+ if self.parent["nodaemon"]:
+ self.parent["logfile"] = None
+ else:
+ self.parent["logfile"] = config.ErrorLogFile
+
+ self.parent["pidfile"] = config.PIDFile
+
+ #
+ # Verify that document root, data root actually exist
+ #
+ self.checkDirectory(
+ config.DocumentRoot,
+ "Document root",
+ # Don't require write access because one might not allow editing on /
+ access=os.R_OK,
+ create=(0750, config.UserName, config.GroupName),
+ )
+ self.checkDirectory(
+ config.DataRoot,
+ "Data root",
+ access=os.W_OK,
+ create=(0750, config.UserName, config.GroupName),
+ )
+
+ #
+ # Nuke the file log observer's time format.
+ #
+
+ if not config.ErrorLogFile and config.ProcessType == "Slave":
+ FileLogObserver.timeFormat = ""
+
+ # Check current umask and warn if changed
+ oldmask = os.umask(config.umask)
+ if oldmask != config.umask:
+ log.info("WARNING: changing umask from: 0%03o to 0%03o"
+ % (oldmask, config.umask,))
+
+ def checkDirectory(self, dirpath, description, access=None, create=None):
+ if not os.path.exists(dirpath):
+ try:
+ mode, username, groupname = create
+ except TypeError:
+ raise ConfigurationError("%s does not exist: %s"
+ % (description, dirpath,))
+ try:
+ os.mkdir(dirpath)
+ except (OSError, IOError), e:
+ log.error("Could not create %s: %s" % (dirpath, e))
+ raise ConfigurationError(
+ "%s does not exist and cannot be created: %s"
+ % (description, dirpath,)
+ )
+
+ if username:
+ uid = getpwnam(username)[2]
+ else:
+ uid = -1
+
+ if groupname:
+ gid = getgrnam(groupname)[2]
+ else:
+ gid = -1
+
+ try:
+ os.chmod(dirpath, mode)
+ os.chown(dirpath, uid, gid)
+ except (OSError, IOError), e:
+ log.error("Unable to change mode/owner of %s: %s"
+ % (dirpath, e))
+
+ log.info("Created directory: %s" % (dirpath,))
+
+ if not os.path.isdir(dirpath):
+ raise ConfigurationError("%s is not a directory: %s"
+ % (description, dirpath,))
+
+ if access and not os.access(dirpath, access):
+ raise ConfigurationError(
+ "Insufficient permissions for server on %s directory: %s"
+ % (description, dirpath,)
+ )
+
+def getSSLPassphrase(*ignored):
+
+ if not config.SSLPrivateKey:
+ return None
+
+ if config.SSLCertAdmin and os.path.isfile(config.SSLCertAdmin):
+ child = Popen(
+ args=[
+ "sudo", config.SSLCertAdmin,
+ "--get-private-key-passphrase", config.SSLPrivateKey,
+ ],
+ stdout=PIPE, stderr=PIPE,
+ )
+ output, error = child.communicate()
+
+ if child.returncode:
+ log.error("Could not get passphrase for %s: %s"
+ % (config.SSLPrivateKey, error))
+ else:
+ return output.strip()
+
+ if config.SSLPassPhraseDialog and os.path.isfile(config.SSLPassPhraseDialog):
+ sslPrivKey = open(config.SSLPrivateKey)
+ try:
+ keyType = None
+ for line in sslPrivKey.readlines():
+ if "-----BEGIN RSA PRIVATE KEY-----" in line:
+ keyType = "RSA"
+ break
+ elif "-----BEGIN DSA PRIVATE KEY-----" in line:
+ keyType = "DSA"
+ break
+ finally:
+ sslPrivKey.close()
+
+ if keyType is None:
+ log.error("Could not get private key type for %s"
+ % (config.SSLPrivateKey,))
+ else:
+ child = Popen(
+ args=[
+ config.SSLPassPhraseDialog,
+ "%s:%s" % (config.ServerHostName, config.SSLPort),
+ keyType,
+ ],
+ stdout=PIPE, stderr=PIPE,
+ )
+ output, error = child.communicate()
+
+ if child.returncode:
+ log.error("Could not get passphrase for %s: %s"
+ % (config.SSLPrivateKey, error))
+ else:
+ return output.strip()
+
+ return None
+
+class ChainingOpenSSLContextFactory (DefaultOpenSSLContextFactory):
+ def __init__(
+ self, privateKeyFileName, certificateFileName,
+ sslmethod=SSL.SSLv23_METHOD, certificateChainFile=None,
+ passwdCallback=None
+ ):
+ self.certificateChainFile = certificateChainFile
+ self.passwdCallback = passwdCallback
+
+ DefaultOpenSSLContextFactory.__init__(
+ self,
+ privateKeyFileName,
+ certificateFileName,
+ sslmethod=sslmethod
+ )
+
+ def cacheContext(self):
+ # Unfortunate code duplication.
+ ctx = SSL.Context(self.sslmethod)
+
+ if self.passwdCallback is not None:
+ ctx.set_passwd_cb(self.passwdCallback)
+
+ ctx.use_certificate_file(self.certificateFileName)
+ ctx.use_privatekey_file(self.privateKeyFileName)
+
+ if self.certificateChainFile != "":
+ ctx.use_certificate_chain_file(self.certificateChainFile)
+
+ self._context = ctx
+
+
+class CalDAVServiceMaker (object):
+ implements(IPlugin, service.IServiceMaker)
+
+ tapname = "caldav"
+ description = "The Darwin Calendar Server"
+ options = CalDAVOptions
+
+ #
+ # Default resource classes
+ #
+ rootResourceClass = RootResource
+ principalResourceClass = DirectoryPrincipalProvisioningResource
+ calendarResourceClass = CalendarHomeProvisioningFile
+ iScheduleResourceClass = IScheduleInboxFile
+ imipResourceClass = IMIPInboxResource
+ timezoneServiceResourceClass = TimezoneServiceFile
+
+ def makeService_Slave(self, options):
+ #
+ # Change default log level to "info" as its useful to have
+ # that during startup
+ #
+ oldLogLevel = logLevelForNamespace(None)
+ setLogLevelForNamespace(None, "info")
+
+ #
+ # Setup the Directory
+ #
+ directories = []
+
+ directoryClass = namedClass(config.DirectoryService.type)
+
+ log.info("Configuring directory service of type: %s"
+ % (config.DirectoryService.type,))
+
+ baseDirectory = directoryClass(**config.DirectoryService.params)
+
+ directories.append(baseDirectory)
+
+ 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:
+ wikiDirectory = WikiDirectoryService()
+ wikiDirectory.realmName = baseDirectory.realmName
+ directories.append(wikiDirectory)
+
+ directory = AggregateDirectoryService(directories)
+
+ if sudoDirectory:
+ directory.userRecordTypes.insert(0,
+ SudoDirectoryService.recordType_sudoers)
+
+ #
+ # Configure Memcached Client Pool
+ #
+ if config.Memcached.ClientEnabled:
+ memcachepool.installPool(
+ IPv4Address(
+ "TCP",
+ config.Memcached.BindAddress,
+ config.Memcached.Port,
+ ),
+ config.Memcached.MaxClients,
+ )
+
+ #
+ # Configure NotificationClient
+ #
+ if config.Notifications.Enabled:
+ installNotificationClient(
+ config.Notifications.InternalNotificationHost,
+ config.Notifications.InternalNotificationPort,
+ )
+
+ #
+ # Setup Resource hierarchy
+ #
+ log.info("Setting up document root at: %s"
+ % (config.DocumentRoot,))
+ log.info("Setting up principal collection: %r"
+ % (self.principalResourceClass,))
+
+ principalCollection = self.principalResourceClass(
+ "/principals/",
+ directory,
+ )
+
+ log.info("Setting up calendar collection: %r"
+ % (self.calendarResourceClass,))
+
+ calendarCollection = self.calendarResourceClass(
+ os.path.join(config.DocumentRoot, "calendars"),
+ directory, "/calendars/",
+ )
+
+ log.info("Setting up root resource: %r" % (self.rootResourceClass,))
+
+ root = self.rootResourceClass(
+ config.DocumentRoot,
+ principalCollections=(principalCollection,),
+ )
+
+ root.putChild("principals", principalCollection)
+ root.putChild("calendars", calendarCollection)
+
+ # Timezone service is optional
+ if config.EnableTimezoneService:
+ log.info("Setting up time zone service resource: %r"
+ % (self.timezoneServiceResourceClass,))
+
+ timezoneService = self.timezoneServiceResourceClass(
+ os.path.join(config.DocumentRoot, "timezones"),
+ root,
+ )
+ root.putChild("timezones", timezoneService)
+
+ # iSchedule service is optional
+ if config.Scheduling.iSchedule.Enabled:
+ log.info("Setting up iSchedule inbox resource: %r"
+ % (self.iScheduleResourceClass,))
+
+ ischedule = self.iScheduleResourceClass(
+ os.path.join(config.DocumentRoot, "ischedule"),
+ root,
+ )
+ root.putChild("ischedule", ischedule)
+
+ #
+ # IMIP delivery resource
+ #
+ log.info("Setting up iMIP inbox resource: %r"
+ % (self.imipResourceClass,))
+
+ imipInbox = self.imipResourceClass(root)
+ root.putChild("inbox", imipInbox)
+
+ #
+ # Configure ancillary data
+ #
+ log.info("Setting up Timezone Cache")
+ TimezoneCache.create()
+
+ #
+ # Configure the Site and Wrappers
+ #
+ credentialFactories = []
+
+ portal = Portal(auth.DavRealm())
+
+ portal.registerChecker(directory)
+
+ realm = directory.realmName or ""
+
+ log.info("Configuring authentication for realm: %s" % (realm,))
+
+ for scheme, schemeConfig in config.Authentication.iteritems():
+ scheme = scheme.lower()
+
+ credFactory = None
+
+ if schemeConfig["Enabled"]:
+ log.info("Setting up scheme: %s" % (scheme,))
+
+ if scheme == "kerberos":
+ if not NegotiateCredentialFactory:
+ log.info("Kerberos support not available")
+ continue
+
+ try:
+ principal = schemeConfig["ServicePrincipal"]
+ if not principal:
+ credFactory = NegotiateCredentialFactory(
+ type="http",
+ hostname=config.ServerHostName,
+ )
+ else:
+ credFactory = NegotiateCredentialFactory(
+ principal=principal,
+ )
+ except ValueError:
+ log.info("Could not start Kerberos")
+ continue
+
+ elif scheme == "digest":
+ credFactory = QopDigestCredentialFactory(
+ schemeConfig["Algorithm"],
+ schemeConfig["Qop"],
+ realm,
+ config.DataRoot,
+ )
+
+ elif scheme == "basic":
+ credFactory = BasicCredentialFactory(realm)
+
+ else:
+ log.error("Unknown scheme: %s" % (scheme,))
+
+ if credFactory:
+ credentialFactories.append(credFactory)
+
+ log.info("Configuring authentication wrapper")
+
+ authWrapper = auth.AuthenticationWrapper(
+ root,
+ portal,
+ credentialFactories,
+ (auth.IPrincipal,),
+ )
+
+ logWrapper = DirectoryLogWrapperResource(
+ authWrapper,
+ directory,
+ )
+
+ #
+ # Configure the service
+ #
+ log.info("Setting up service")
+
+ if config.ProcessType == "Slave":
+ if (
+ config.MultiProcess.ProcessCount > 1 and
+ config.MultiProcess.LoadBalancer.Enabled
+ ):
+ realRoot = pdmonster.PDClientAddressWrapper(
+ logWrapper,
+ config.PythonDirector.ControlSocket,
+ directory,
+ )
+ else:
+ realRoot = logWrapper
+
+ logObserver = AMPCommonAccessLoggingObserver(
+ config.ControlSocket,
+ )
+
+ elif config.ProcessType == "Single":
+ realRoot = logWrapper
+
+ logObserver = RotatingFileAccessLoggingObserver(
+ config.AccessLogFile,
+ )
+
+ log.info("Configuring log observer: %s" % (logObserver,))
+
+ service = CalDAVService(logObserver)
+
+ site = Site(realRoot)
+
+ channel = HTTP503LoggingFactory(
+ site,
+ maxRequests=config.MaxRequests,
+ betweenRequestsTimeOut=config.IdleConnectionTimeOut,
+ )
+
+ def updateChannel(config, items):
+ channel.maxRequests = config.MaxRequests
+
+ config.addHook(updateChannel)
+
+ if not config.BindAddresses:
+ config.BindAddresses = [""]
+
+ for bindAddress in config.BindAddresses:
+ if config.BindHTTPPorts:
+ if config.HTTPPort == 0:
+ raise UsageError(
+ "HTTPPort required if BindHTTPPorts is not empty"
+ )
+ elif config.HTTPPort != 0:
+ config.BindHTTPPorts = [config.HTTPPort]
+
+ if config.BindSSLPorts:
+ if config.SSLPort == 0:
+ raise UsageError(
+ "SSLPort required if BindSSLPorts is not empty"
+ )
+ elif config.SSLPort != 0:
+ config.BindSSLPorts = [config.SSLPort]
+
+ for port in config.BindHTTPPorts:
+ log.info("Adding server at %s:%s" % (bindAddress, port))
+
+ httpService = internet.TCPServer(
+ int(port), channel,
+ interface=bindAddress,
+ backlog=config.ListenBacklog,
+ )
+ httpService.setServiceParent(service)
+
+ for port in config.BindSSLPorts:
+ log.info("Adding SSL server at %s:%s" % (bindAddress, port))
+
+ try:
+ contextFactory = ChainingOpenSSLContextFactory(
+ config.SSLPrivateKey,
+ config.SSLCertificate,
+ certificateChainFile=config.SSLAuthorityChain,
+ passwdCallback=getSSLPassphrase,
+ )
+ except SSL.Error, e:
+ log.error("Unable to set up SSL context factory: %s" % (e,))
+ log.error("Disabling SSL port: %s" % (port,))
+ else:
+ httpsService = internet.SSLServer(
+ int(port), channel,
+ contextFactory, interface=bindAddress,
+ backlog=config.ListenBacklog,
+ )
+ httpsService.setServiceParent(service)
+
+ # Change log level back to what it was before
+ setLogLevelForNamespace(None, oldLogLevel)
+
+ return service
+
+ makeService_Combined = makeService_Combined
+ makeService_Master = makeService_Master
+ makeService_Single = makeService_Slave
+
+ def makeService(self, options):
+
+ # Now do any on disk upgrades we might need.
+ UpgradeTheServer.doUpgrade()
+
+ serverType = config.ProcessType
+
+ serviceMethod = getattr(self, "makeService_%s" % (serverType,), None)
+
+ if not serviceMethod:
+ raise UsageError(
+ "Unknown server type %s. "
+ "Please choose: Master, Slave, Single or Combined"
+ % (serverType,)
+ )
+ else:
+ service = serviceMethod(options)
+
+ #
+ # Note: if there is a stopped process in the same session
+ # as the calendar server and the calendar server is the
+ # group leader then when twistd forks to drop privileges a
+ # SIGHUP may be sent by the kernel, which can cause the
+ # process to exit. This SIGHUP should be, at a minimum,
+ # ignored.
+ #
+
+ def location(frame):
+ if frame is None:
+ return "Unknown"
+ else:
+ return "%s: %s" % (frame.f_code.co_name, frame.f_lineno)
+
+ import signal
+ def sighup_handler(num, frame):
+ log.info("SIGHUP recieved at %s" % (location(frame),))
+
+ # Reload the config file
+ config.reload()
+
+ # If combined service send signal to all caldavd children
+ if serverType == "Combined":
+ service.processMonitor.signalAll(signal.SIGHUP, "caldav")
+
+ # FIXME: There is no memcachepool.getCachePool
+ # Also, better option is probably to add a hook to
+ # the config object instead of doing things here.
+ #log.info("Suggesting new max clients for memcache.")
+ #memcachepool.getCachePool().suggestMaxClients(
+ # config.Memcached.MaxClients
+ #)
+
+ signal.signal(signal.SIGHUP, sighup_handler)
+
+ return service
Copied: CalendarServer/trunk/calendarserver/test/test_tap.py (from rev 3399, CalendarServer/trunk/twistedcaldav/test/test_tap.py)
===================================================================
--- CalendarServer/trunk/calendarserver/test/test_tap.py (rev 0)
+++ CalendarServer/trunk/calendarserver/test/test_tap.py 2008-11-20 18:43:50 UTC (rev 3402)
@@ -0,0 +1,684 @@
+##
+# Copyright (c) 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
+
+try:
+ from plistlib import writePlist
+except ImportError:
+ from twistedcaldav.py.plistlib import writePlist
+
+from twisted.trial import unittest
+
+from twisted.python.usage import Options, UsageError
+from twisted.python.util import sibpath
+from twisted.python.reflect import namedAny
+from twisted.application.service import IService
+from twisted.application import internet
+
+from twisted.web2.dav import auth
+from twisted.web2.log import LogWrapperResource
+
+from twistedcaldav.config import config, ConfigDict, defaultConfig, _mergeData
+
+from twistedcaldav.directory.aggregate import AggregateDirectoryService
+from twistedcaldav.directory.sudo import SudoDirectoryService
+from twistedcaldav.directory.directory import UnknownRecordTypeError
+
+from calendarserver.tap.caldav import CalDAVOptions, CalDAVServiceMaker, CalDAVService
+
+class TestCalDAVOptions (CalDAVOptions):
+ """
+ A fake implementation of CalDAVOptions that provides
+ empty implementations of checkDirectory and checkFile.
+ """
+ def checkDirectory(*args, **kwargs):
+ pass
+
+ def checkFile(*args, **kwargs):
+ pass
+
+
+class CalDAVOptionsTest (unittest.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.
+ """
+ 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.loadConfig(None)
+ config.setDefaults(defaultConfig)
+ config.reload()
+
+ def test_overridesConfig(self):
+ """
+ Test that values on the command line's -o and --option options
+ overide the config file
+ """
+ argv = [
+ "-f", "No-Such-File",
+ "-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)
+ """
+ argv = [
+ "-f", "No-Such-File",
+ "-o", "ErrorLogFile=/dev/null",
+ "-o", "PIDFile=/dev/null",
+ ]
+
+ self.config.parseOptions(argv)
+
+ self.assertEquals(self.config.parent["logfile"], "/dev/null")
+ 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(defaultConfig)
+
+ myConfig.Authentication.Basic.Enabled = False
+ myConfig.MultiProcess.LoadBalancer.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.MultiProcess.LoadBalancer.Enabled,
+ myConfig.MultiProcess.LoadBalancer.Enabled
+ )
+ 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"
+ """
+ argv = [
+ "-o", "MultiProcess/ProcessCount=102",
+ "-f", "conf/caldavd.plist",
+ ]
+
+ self.config.parseOptions(argv)
+
+ self.assertEquals(config.MultiProcess["ProcessCount"], 102)
+
+class BaseServiceMakerTests(unittest.TestCase):
+ """
+ Utility class for ServiceMaker tests.
+ """
+ configOptions = None
+
+ def 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(defaultConfig)
+
+ sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+ accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml")
+ pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
+
+ self.config["DirectoryService"] = {
+ "params": {"xmlFile": accountsFile},
+ "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService"
+ }
+
+ self.config.DocumentRoot = self.mktemp()
+ self.config.DataRoot = self.mktemp()
+ self.config.ProcessType = "Slave"
+ self.config.SSLPrivateKey = pemFile
+ self.config.SSLCertificate = pemFile
+
+ self.config.SudoersFile = ""
+
+ if self.configOptions:
+ _mergeData(self.config, self.configOptions)
+
+ os.mkdir(self.config.DocumentRoot)
+ os.mkdir(self.config.DataRoot)
+
+ self.configFile = self.mktemp()
+
+ self.writeConfig()
+
+ def tearDown(self):
+ config.loadConfig(None)
+ config.setDefaults(defaultConfig)
+ config.reload()
+
+ 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()
+
+ return service.services[0].args[1].protocolArgs["requestFactory"]
+
+
+class CalDAVServiceMakerTests(BaseServiceMakerTests):
+ """
+ Test the service maker's behavior
+ """
+
+ def test_makeServiceDispatcher(self):
+ """
+ Test the default options of the dispatching makeService
+ """
+ validServices = ["Slave", "Master", "Combined"]
+
+ self.config["HTTPPort"] = 80
+
+ for service in validServices:
+ self.config["ProcessType"] = service
+ self.writeConfig()
+ self.makeService()
+
+ self.config["ProcessType"] = "Unknown Service"
+ self.writeConfig()
+ self.assertRaises(UsageError, self.makeService)
+
+
+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 = (
+ (internet.TCPServer, self.config["HTTPPort"]),
+ (internet.SSLServer, self.config["SSLPort"]),
+ )
+
+ configuredSubServices = [(s.__class__, s.args) for s in service.services]
+
+ for serviceClass, serviceArgs in configuredSubServices:
+ self.failUnless(serviceClass in (s[0] for s in expectedSubServices))
+
+ self.assertEquals(
+ serviceArgs[0],
+ dict(expectedSubServices)[serviceClass]
+ )
+
+ 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:
+ 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:
+ 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, CalDAVServiceMaker.rootResourceClass))
+
+ def test_principalResource(self):
+ """
+ Test the principal resource
+ """
+ site = self.getSite()
+ root = site.resource.resource.resource
+
+ self.failUnless(isinstance(
+ root.getChild("principals"),
+ CalDAVServiceMaker.principalResourceClass
+ ))
+
+ def test_calendarResource(self):
+ """
+ Test the calendar resource
+ """
+ site = self.getSite()
+ root = site.resource.resource.resource
+
+ self.failUnless(isinstance(
+ root.getChild("calendars"),
+ CalDAVServiceMaker.calendarResourceClass
+ ))
+
+
+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 = self.mktemp()
+
+ self.writeConfig()
+
+ open(self.config.SudoersFile, "w").write(sudoersFile)
+
+ site = self.getSite()
+ principals = site.resource.resource.resource.getChild("principals")
+ directory = principals.directory
+
+ self.failUnless(self.config.SudoersFile)
+
+ sudoService = directory.serviceForRecordType(SudoDirectoryService.recordType_sudoers)
+
+ self.assertEquals(
+ sudoService.plistFile.path,
+ os.path.abspath(self.config.SudoersFile)
+ )
+ 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))
Modified: CalendarServer/trunk/test
===================================================================
--- CalendarServer/trunk/test 2008-11-20 18:37:45 UTC (rev 3401)
+++ CalendarServer/trunk/test 2008-11-20 18:43:50 UTC (rev 3402)
@@ -62,7 +62,7 @@
if [ $# -gt 0 ]; then
test_modules="$@";
else
- test_modules="twistedcaldav twisted";
+ test_modules="calendarserver twistedcaldav twisted";
fi;
cd "${wd}" && "${twisted}/bin/trial" ${random} ${until_fail} ${no_colour} ${coverage} ${test_modules};
Modified: CalendarServer/trunk/twisted/plugins/caldav.py
===================================================================
--- CalendarServer/trunk/twisted/plugins/caldav.py 2008-11-20 18:37:45 UTC (rev 3401)
+++ CalendarServer/trunk/twisted/plugins/caldav.py 2008-11-20 18:43:50 UTC (rev 3402)
@@ -17,9 +17,9 @@
self.serviceMakerClass = serviceMakerClass
self._serviceMaker = None
- options = serviceMakerProperty('options')
- tapname = serviceMakerProperty('tapname')
- description = serviceMakerProperty('description')
+ options = serviceMakerProperty("options")
+ tapname = serviceMakerProperty("tapname")
+ description = serviceMakerProperty("description")
def makeService(self, options):
if self._serviceMaker is None:
@@ -28,7 +28,6 @@
return self._serviceMaker.makeService(options)
-TwistedCalDAV = TAP('twistedcaldav.tap.CalDAVServiceMaker')
-
-CalDAVNotifier = TAP('twistedcaldav.notify.NotificationServiceMaker')
-CalDAVMailGateway = TAP('twistedcaldav.mail.MailGatewayServiceMaker')
+TwistedCalDAV = TAP("calendarserver.tap.caldav.CalDAVServiceMaker")
+CalDAVNotifier = TAP("twistedcaldav.notify.NotificationServiceMaker")
+CalDAVMailGateway = TAP("twistedcaldav.mail.MailGatewayServiceMaker")
Modified: CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py 2008-11-20 18:37:45 UTC (rev 3401)
+++ CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py 2008-11-20 18:43:50 UTC (rev 3402)
@@ -123,7 +123,7 @@
try:
from twisted.internet import reactor
if self.server.ssl:
- from twistedcaldav.tap import ChainingOpenSSLContextFactory
+ from calendarserver.tap.caldav import ChainingOpenSSLContextFactory
context = ChainingOpenSSLContextFactory(config.SSLPrivateKey, config.SSLCertificate, certificateChainFile=config.SSLAuthorityChain)
proto = (yield ClientCreator(reactor, HTTPClientProtocol).connectSSL(self.server.host, self.server.port, context))
else:
Deleted: CalendarServer/trunk/twistedcaldav/tap.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/tap.py 2008-11-20 18:37:45 UTC (rev 3401)
+++ CalendarServer/trunk/twistedcaldav/tap.py 2008-11-20 18:43:50 UTC (rev 3402)
@@ -1,759 +0,0 @@
-##
-# Copyright (c) 2005-2008 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 subprocess import Popen, PIPE
-from pwd import getpwnam
-from grp import getgrnam
-from OpenSSL import SSL
-
-from zope.interface import implements
-
-from twisted.internet.ssl import DefaultOpenSSLContextFactory
-from twisted.internet.address import IPv4Address
-from twisted.python.log import FileLogObserver
-from twisted.python.usage import Options, UsageError
-from twisted.python.reflect import namedClass
-from twisted.application import internet, service
-from twisted.plugin import IPlugin
-from twisted.scripts.mktap import getid
-from twisted.cred.portal import Portal
-from twisted.web2.dav import auth
-from twisted.web2.auth.basic import BasicCredentialFactory
-from twisted.web2.server import Site
-
-from twistedcaldav.log import Logger, logLevelForNamespace, setLogLevelForNamespace
-from twistedcaldav.accesslog import DirectoryLogWrapperResource
-from twistedcaldav.accesslog import RotatingFileAccessLoggingObserver
-from twistedcaldav.accesslog import AMPCommonAccessLoggingObserver
-from twistedcaldav.cluster import makeService_Combined, makeService_Master
-from twistedcaldav.config import config, parseConfig, defaultConfig, defaultConfigFile, ConfigurationError
-from twistedcaldav.root import RootResource
-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.directory.digest import QopDigestCredentialFactory
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.directory.wiki import WikiDirectoryService
-from twistedcaldav.httpfactory import HTTP503LoggingFactory
-from twistedcaldav.static import CalendarHomeProvisioningFile
-from twistedcaldav.static import IScheduleInboxFile
-from twistedcaldav.static import TimezoneServiceFile
-from twistedcaldav.mail import IMIPInboxResource
-from twistedcaldav.timezones import TimezoneCache
-from twistedcaldav.upgrade import UpgradeTheServer
-from twistedcaldav import pdmonster
-from twistedcaldav import memcachepool
-from twistedcaldav.notify import installNotificationClient
-
-log = Logger()
-
-try:
- from twistedcaldav.authkerb import NegotiateCredentialFactory
-except ImportError:
- NegotiateCredentialFactory = None
-
-
-class CalDAVService(service.MultiService):
- def __init__(self, logObserver):
- self.logObserver = logObserver
- service.MultiService.__init__(self)
-
- def privilegedStartService(self):
- service.MultiService.privilegedStartService(self)
- self.logObserver.start()
-
- def stopService(self):
- service.MultiService.stopService(self)
- self.logObserver.stop()
-
-
-class CalDAVOptions(Options):
- optParameters = [[
- "config", "f", defaultConfigFile, "Path to configuration file."
- ]]
-
- zsh_actions = {"config" : "_files -g '*.plist'"}
-
- def __init__(self, *args, **kwargs):
- super(CalDAVOptions, self).__init__(*args, **kwargs)
-
- self.overrides = {}
-
- @staticmethod
- def coerceOption(configDict, key, value):
- """
- Coerce the given C{val} to type of C{configDict[key]}
- """
- if key in configDict:
- if isinstance(configDict[key], bool):
- value = value == "True"
-
- elif isinstance(configDict[key], (int, float, long)):
- value = type(configDict[key])(value)
-
- elif isinstance(configDict[key], (list, tuple)):
- value = value.split(",")
-
- elif isinstance(configDict[key], dict):
- raise UsageError(
- "Dict options not supported on the command line"
- )
-
- elif value == "None":
- value = None
-
- return value
-
- @classmethod
- def setOverride(cls, configDict, path, value, overrideDict):
- """
- Set the value at path in configDict
- """
- key = path[0]
-
- if len(path) == 1:
- overrideDict[key] = cls.coerceOption(configDict, key, value)
- return
-
- if key in configDict:
- if not isinstance(configDict[key], dict):
- raise UsageError(
- "Found intermediate path element that is not a dictionary"
- )
-
- if key not in overrideDict:
- overrideDict[key] = {}
-
- cls.setOverride(
- configDict[key], path[1:],
- value, overrideDict[key]
- )
-
- def opt_option(self, option):
- """
- Set an option to override a value in the config file. True, False, int,
- and float options are supported, as well as comma seperated lists. Only
- one option may be given for each --option flag, however multiple
- --option flags may be specified.
- """
- if "=" in option:
- path, value = option.split("=")
- self.setOverride(
- defaultConfig,
- path.split("/"),
- value,
- self.overrides
- )
- else:
- self.opt_option("%s=True" % (option,))
-
- opt_o = opt_option
-
- def postOptions(self):
- if not os.path.exists(self["config"]):
- log.info("Config file %s not found, using defaults"
- % (self["config"],))
-
- log.info("Reading configuration from file: %s" % (self["config"],))
- parseConfig(self["config"])
-
- config.updateDefaults(self.overrides)
-
- uid, gid = None, None
-
- if self.parent["uid"] or self.parent["gid"]:
- uid, gid = getid(self.parent["uid"], self.parent["gid"])
-
- def gottaBeRoot():
- if os.getuid() != 0:
- import pwd
- username = pwd.getpwuid(os.getuid())[0]
- raise UsageError("Only root can drop privileges. You are: %r"
- % (username,))
-
- if uid and uid != os.getuid():
- gottaBeRoot()
-
- if gid and gid != os.getgid():
- gottaBeRoot()
-
- #
- # Ignore the logfile parameter if not daemonized and log to stdout.
- #
- if self.parent["nodaemon"]:
- self.parent["logfile"] = None
- else:
- self.parent["logfile"] = config.ErrorLogFile
-
- self.parent["pidfile"] = config.PIDFile
-
- #
- # Verify that document root, data root actually exist
- #
- self.checkDirectory(
- config.DocumentRoot,
- "Document root",
- # Don't require write access because one might not allow editing on /
- access=os.R_OK,
- create=(0750, config.UserName, config.GroupName),
- )
- self.checkDirectory(
- config.DataRoot,
- "Data root",
- access=os.W_OK,
- create=(0750, config.UserName, config.GroupName),
- )
-
- #
- # Nuke the file log observer's time format.
- #
-
- if not config.ErrorLogFile and config.ProcessType == "Slave":
- FileLogObserver.timeFormat = ""
-
- # Check current umask and warn if changed
- oldmask = os.umask(config.umask)
- if oldmask != config.umask:
- log.info("WARNING: changing umask from: 0%03o to 0%03o"
- % (oldmask, config.umask,))
-
- def checkDirectory(self, dirpath, description, access=None, create=None):
- if not os.path.exists(dirpath):
- try:
- mode, username, groupname = create
- except TypeError:
- raise ConfigurationError("%s does not exist: %s"
- % (description, dirpath,))
- try:
- os.mkdir(dirpath)
- except (OSError, IOError), e:
- log.error("Could not create %s: %s" % (dirpath, e))
- raise ConfigurationError(
- "%s does not exist and cannot be created: %s"
- % (description, dirpath,)
- )
-
- if username:
- uid = getpwnam(username)[2]
- else:
- uid = -1
-
- if groupname:
- gid = getgrnam(groupname)[2]
- else:
- gid = -1
-
- try:
- os.chmod(dirpath, mode)
- os.chown(dirpath, uid, gid)
- except (OSError, IOError), e:
- log.error("Unable to change mode/owner of %s: %s"
- % (dirpath, e))
-
- log.info("Created directory: %s" % (dirpath,))
-
- if not os.path.isdir(dirpath):
- raise ConfigurationError("%s is not a directory: %s"
- % (description, dirpath,))
-
- if access and not os.access(dirpath, access):
- raise ConfigurationError(
- "Insufficient permissions for server on %s directory: %s"
- % (description, dirpath,)
- )
-
-def getSSLPassphrase(*ignored):
-
- if not config.SSLPrivateKey:
- return None
-
- if config.SSLCertAdmin and os.path.isfile(config.SSLCertAdmin):
- child = Popen(
- args=[
- "sudo", config.SSLCertAdmin,
- "--get-private-key-passphrase", config.SSLPrivateKey,
- ],
- stdout=PIPE, stderr=PIPE,
- )
- output, error = child.communicate()
-
- if child.returncode:
- log.error("Could not get passphrase for %s: %s"
- % (config.SSLPrivateKey, error))
- else:
- return output.strip()
-
- if config.SSLPassPhraseDialog and os.path.isfile(config.SSLPassPhraseDialog):
- sslPrivKey = open(config.SSLPrivateKey)
- try:
- keyType = None
- for line in sslPrivKey.readlines():
- if "-----BEGIN RSA PRIVATE KEY-----" in line:
- keyType = "RSA"
- break
- elif "-----BEGIN DSA PRIVATE KEY-----" in line:
- keyType = "DSA"
- break
- finally:
- sslPrivKey.close()
-
- if keyType is None:
- log.error("Could not get private key type for %s"
- % (config.SSLPrivateKey,))
- else:
- child = Popen(
- args=[
- config.SSLPassPhraseDialog,
- "%s:%s" % (config.ServerHostName, config.SSLPort),
- keyType,
- ],
- stdout=PIPE, stderr=PIPE,
- )
- output, error = child.communicate()
-
- if child.returncode:
- log.error("Could not get passphrase for %s: %s"
- % (config.SSLPrivateKey, error))
- else:
- return output.strip()
-
- return None
-
-class ChainingOpenSSLContextFactory(DefaultOpenSSLContextFactory):
- def __init__(
- self, privateKeyFileName, certificateFileName,
- sslmethod=SSL.SSLv23_METHOD, certificateChainFile=None,
- passwdCallback=None
- ):
- self.certificateChainFile = certificateChainFile
- self.passwdCallback = passwdCallback
-
- DefaultOpenSSLContextFactory.__init__(
- self,
- privateKeyFileName,
- certificateFileName,
- sslmethod=sslmethod
- )
-
- def cacheContext(self):
- # Unfortunate code duplication.
- ctx = SSL.Context(self.sslmethod)
-
- if self.passwdCallback is not None:
- ctx.set_passwd_cb(self.passwdCallback)
-
- ctx.use_certificate_file(self.certificateFileName)
- ctx.use_privatekey_file(self.privateKeyFileName)
-
- if self.certificateChainFile != "":
- ctx.use_certificate_chain_file(self.certificateChainFile)
-
- self._context = ctx
-
-
-class CalDAVServiceMaker(object):
- implements(IPlugin, service.IServiceMaker)
-
- tapname = "caldav"
- description = "The Darwin Calendar Server"
- options = CalDAVOptions
-
- #
- # Default resource classes
- #
- rootResourceClass = RootResource
- principalResourceClass = DirectoryPrincipalProvisioningResource
- calendarResourceClass = CalendarHomeProvisioningFile
- iScheduleResourceClass = IScheduleInboxFile
- imipResourceClass = IMIPInboxResource
- timezoneServiceResourceClass = TimezoneServiceFile
-
- def makeService_Slave(self, options):
- #
- # Change default log level to "info" as its useful to have
- # that during startup
- #
- oldLogLevel = logLevelForNamespace(None)
- setLogLevelForNamespace(None, "info")
-
- #
- # Setup the Directory
- #
- directories = []
-
- directoryClass = namedClass(config.DirectoryService.type)
-
- log.info("Configuring directory service of type: %s"
- % (config.DirectoryService.type,))
-
- baseDirectory = directoryClass(**config.DirectoryService.params)
-
- directories.append(baseDirectory)
-
- 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:
- wikiDirectory = WikiDirectoryService()
- wikiDirectory.realmName = baseDirectory.realmName
- directories.append(wikiDirectory)
-
- directory = AggregateDirectoryService(directories)
-
- if sudoDirectory:
- directory.userRecordTypes.insert(0,
- SudoDirectoryService.recordType_sudoers)
-
- #
- # Configure Memcached Client Pool
- #
- if config.Memcached.ClientEnabled:
- memcachepool.installPool(
- IPv4Address(
- "TCP",
- config.Memcached.BindAddress,
- config.Memcached.Port,
- ),
- config.Memcached.MaxClients,
- )
-
- #
- # Configure NotificationClient
- #
- if config.Notifications.Enabled:
- installNotificationClient(
- config.Notifications.InternalNotificationHost,
- config.Notifications.InternalNotificationPort,
- )
-
- #
- # Setup Resource hierarchy
- #
- log.info("Setting up document root at: %s"
- % (config.DocumentRoot,))
- log.info("Setting up principal collection: %r"
- % (self.principalResourceClass,))
-
- principalCollection = self.principalResourceClass(
- "/principals/",
- directory,
- )
-
- log.info("Setting up calendar collection: %r"
- % (self.calendarResourceClass,))
-
- calendarCollection = self.calendarResourceClass(
- os.path.join(config.DocumentRoot, "calendars"),
- directory, "/calendars/",
- )
-
- log.info("Setting up root resource: %r" % (self.rootResourceClass,))
-
- root = self.rootResourceClass(
- config.DocumentRoot,
- principalCollections=(principalCollection,),
- )
-
- root.putChild("principals", principalCollection)
- root.putChild("calendars", calendarCollection)
-
- # Timezone service is optional
- if config.EnableTimezoneService:
- log.info("Setting up time zone service resource: %r"
- % (self.timezoneServiceResourceClass,))
-
- timezoneService = self.timezoneServiceResourceClass(
- os.path.join(config.DocumentRoot, "timezones"),
- root,
- )
- root.putChild("timezones", timezoneService)
-
- # iSchedule service is optional
- if config.Scheduling.iSchedule.Enabled:
- log.info("Setting up iSchedule inbox resource: %r"
- % (self.iScheduleResourceClass,))
-
- ischedule = self.iScheduleResourceClass(
- os.path.join(config.DocumentRoot, "ischedule"),
- root,
- )
- root.putChild("ischedule", ischedule)
-
- #
- # IMIP delivery resource
- #
- log.info("Setting up iMIP inbox resource: %r"
- % (self.imipResourceClass,))
-
- imipInbox = self.imipResourceClass(root)
- root.putChild("inbox", imipInbox)
-
- #
- # Configure ancillary data
- #
- log.info("Setting up Timezone Cache")
- TimezoneCache.create()
-
- #
- # Configure the Site and Wrappers
- #
- credentialFactories = []
-
- portal = Portal(auth.DavRealm())
-
- portal.registerChecker(directory)
-
- realm = directory.realmName or ""
-
- log.info("Configuring authentication for realm: %s" % (realm,))
-
- for scheme, schemeConfig in config.Authentication.iteritems():
- scheme = scheme.lower()
-
- credFactory = None
-
- if schemeConfig["Enabled"]:
- log.info("Setting up scheme: %s" % (scheme,))
-
- if scheme == "kerberos":
- if not NegotiateCredentialFactory:
- log.info("Kerberos support not available")
- continue
-
- try:
- principal = schemeConfig["ServicePrincipal"]
- if not principal:
- credFactory = NegotiateCredentialFactory(
- type="http",
- hostname=config.ServerHostName,
- )
- else:
- credFactory = NegotiateCredentialFactory(
- principal=principal,
- )
- except ValueError:
- log.info("Could not start Kerberos")
- continue
-
- elif scheme == "digest":
- credFactory = QopDigestCredentialFactory(
- schemeConfig["Algorithm"],
- schemeConfig["Qop"],
- realm,
- config.DataRoot,
- )
-
- elif scheme == "basic":
- credFactory = BasicCredentialFactory(realm)
-
- else:
- log.error("Unknown scheme: %s" % (scheme,))
-
- if credFactory:
- credentialFactories.append(credFactory)
-
- log.info("Configuring authentication wrapper")
-
- authWrapper = auth.AuthenticationWrapper(
- root,
- portal,
- credentialFactories,
- (auth.IPrincipal,),
- )
-
- logWrapper = DirectoryLogWrapperResource(
- authWrapper,
- directory,
- )
-
- #
- # Configure the service
- #
- log.info("Setting up service")
-
- if config.ProcessType == "Slave":
- if (
- config.MultiProcess.ProcessCount > 1 and
- config.MultiProcess.LoadBalancer.Enabled
- ):
- realRoot = pdmonster.PDClientAddressWrapper(
- logWrapper,
- config.PythonDirector.ControlSocket,
- directory,
- )
- else:
- realRoot = logWrapper
-
- logObserver = AMPCommonAccessLoggingObserver(
- config.ControlSocket,
- )
-
- elif config.ProcessType == "Single":
- realRoot = logWrapper
-
- logObserver = RotatingFileAccessLoggingObserver(
- config.AccessLogFile,
- )
-
- log.info("Configuring log observer: %s" % (logObserver,))
-
- service = CalDAVService(logObserver)
-
- site = Site(realRoot)
-
- channel = HTTP503LoggingFactory(
- site,
- maxRequests=config.MaxRequests,
- betweenRequestsTimeOut=config.IdleConnectionTimeOut,
- )
-
- def updateChannel(config, items):
- channel.maxRequests = config.MaxRequests
-
- config.addHook(updateChannel)
-
- if not config.BindAddresses:
- config.BindAddresses = [""]
-
- for bindAddress in config.BindAddresses:
- if config.BindHTTPPorts:
- if config.HTTPPort == 0:
- raise UsageError(
- "HTTPPort required if BindHTTPPorts is not empty"
- )
- elif config.HTTPPort != 0:
- config.BindHTTPPorts = [config.HTTPPort]
-
- if config.BindSSLPorts:
- if config.SSLPort == 0:
- raise UsageError(
- "SSLPort required if BindSSLPorts is not empty"
- )
- elif config.SSLPort != 0:
- config.BindSSLPorts = [config.SSLPort]
-
- for port in config.BindHTTPPorts:
- log.info("Adding server at %s:%s" % (bindAddress, port))
-
- httpService = internet.TCPServer(
- int(port), channel,
- interface=bindAddress,
- backlog=config.ListenBacklog,
- )
- httpService.setServiceParent(service)
-
- for port in config.BindSSLPorts:
- log.info("Adding SSL server at %s:%s" % (bindAddress, port))
-
- try:
- contextFactory = ChainingOpenSSLContextFactory(
- config.SSLPrivateKey,
- config.SSLCertificate,
- certificateChainFile=config.SSLAuthorityChain,
- passwdCallback=getSSLPassphrase,
- )
- except SSL.Error, e:
- log.error("Unable to set up SSL context factory: %s" % (e,))
- log.error("Disabling SSL port: %s" % (port,))
- else:
- httpsService = internet.SSLServer(
- int(port), channel,
- contextFactory, interface=bindAddress,
- backlog=config.ListenBacklog,
- )
- httpsService.setServiceParent(service)
-
- # Change log level back to what it was before
- setLogLevelForNamespace(None, oldLogLevel)
-
- return service
-
- makeService_Combined = makeService_Combined
- makeService_Master = makeService_Master
- makeService_Single = makeService_Slave
-
- def makeService(self, options):
-
- # Now do any on disk upgrades we might need.
- UpgradeTheServer.doUpgrade()
-
- serverType = config.ProcessType
-
- serviceMethod = getattr(self, "makeService_%s" % (serverType,), None)
-
- if not serviceMethod:
- raise UsageError(
- "Unknown server type %s. "
- "Please choose: Master, Slave, Single or Combined"
- % (serverType,)
- )
- else:
- service = serviceMethod(options)
-
- #
- # Note: if there is a stopped process in the same session
- # as the calendar server and the calendar server is the
- # group leader then when twistd forks to drop privileges a
- # SIGHUP may be sent by the kernel, which can cause the
- # process to exit. This SIGHUP should be, at a minimum,
- # ignored.
- #
-
- def location(frame):
- if frame is None:
- return "Unknown"
- else:
- return "%s: %s" % (frame.f_code.co_name, frame.f_lineno)
-
- import signal
- def sighup_handler(num, frame):
- log.info("SIGHUP recieved at %s" % (location(frame),))
-
- # Reload the config file
- config.reload()
-
- # If combined service send signal to all caldavd children
- if serverType == "Combined":
- service.processMonitor.signalAll(signal.SIGHUP, "caldav")
-
- # FIXME: There is no memcachepool.getCachePool
- # Also, better option is probably to add a hook to
- # the config object instead of doing things here.
- #log.info("Suggesting new max clients for memcache.")
- #memcachepool.getCachePool().suggestMaxClients(
- # config.Memcached.MaxClients
- #)
-
- signal.signal(signal.SIGHUP, sighup_handler)
-
- return service
Deleted: CalendarServer/trunk/twistedcaldav/test/test_tap.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_tap.py 2008-11-20 18:37:45 UTC (rev 3401)
+++ CalendarServer/trunk/twistedcaldav/test/test_tap.py 2008-11-20 18:43:50 UTC (rev 3402)
@@ -1,674 +0,0 @@
-##
-# Copyright (c) 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 copy import deepcopy
-
-try:
- from plistlib import writePlist
-except ImportError:
- from twistedcaldav.py.plistlib import writePlist
-
-from twisted.trial import unittest
-
-from twisted.python.usage import Options, UsageError
-from twisted.python.util import sibpath
-from twisted.python.reflect import namedAny
-from twisted.application.service import IService
-from twisted.application import internet
-
-from twisted.web2.dav import auth
-from twisted.web2.log import LogWrapperResource
-
-from twistedcaldav.tap import CalDAVOptions, CalDAVServiceMaker
-from twistedcaldav import tap
-
-from twistedcaldav.config import config
-from twistedcaldav import config as config_mod
-
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-
-
-class TestCalDAVOptions(CalDAVOptions):
- """
- A fake implementation of CalDAVOptions that provides
- empty implementations of checkDirectory and checkFile.
- """
-
- def checkDirectory(*args, **kwargs):
- pass
-
- def checkFile(*args, **kwargs):
- pass
-
-
-class CalDAVOptionsTest(unittest.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.
- """
- 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.loadConfig(None)
- config.setDefaults(config_mod.defaultConfig)
- config.reload()
-
- def test_overridesConfig(self):
- """
- Test that values on the command line's -o and --option options
- overide the config file
- """
-
- argv = ['-f', 'No-Such-File',
- '-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)
- """
-
- argv = ['-f', 'No-Such-File',
- '-o', 'ErrorLogFile=/dev/null',
- '-o', 'PIDFile=/dev/null']
-
- self.config.parseOptions(argv)
-
- self.assertEquals(self.config.parent['logfile'], '/dev/null')
-
- 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 = deepcopy(config_mod.defaultConfig)
-
- myConfig['Authentication']['Basic']['Enabled'] = False
-
- myConfig['MultiProcess']['LoadBalancer']['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.MultiProcess['LoadBalancer']['Enabled'],
- myConfig['MultiProcess']['LoadBalancer']['Enabled'])
-
- 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'
- """
-
- argv = ['-o', 'MultiProcess/ProcessCount=102', '-f', 'conf/caldavd.plist',]
- self.config.parseOptions(argv)
-
- self.assertEquals(config.MultiProcess['ProcessCount'], 102)
-
-class BaseServiceMakerTests(unittest.TestCase):
- """
- Utility class for ServiceMaker tests.
- """
-
- configOptions = None
-
- def 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 = deepcopy(config_mod.defaultConfig)
-
- accountsFile = sibpath(os.path.dirname(__file__),
- 'directory/test/accounts.xml')
-
- self.config['DirectoryService'] = {
- 'params': {'xmlFile': accountsFile},
- 'type': 'twistedcaldav.directory.xmlfile.XMLDirectoryService'
- }
-
- self.config['DocumentRoot'] = self.mktemp()
- self.config['DataRoot'] = self.mktemp()
- self.config['ProcessType'] = 'Slave'
- self.config['SSLPrivateKey'] = sibpath(__file__, 'data/server.pem')
- self.config['SSLCertificate'] = sibpath(__file__, 'data/server.pem')
-
- self.config['SudoersFile'] = ''
-
- if self.configOptions:
- config_mod._mergeData(self.config, self.configOptions)
-
- os.mkdir(self.config['DocumentRoot'])
- os.mkdir(self.config['DataRoot'])
-
- self.configFile = self.mktemp()
-
- self.writeConfig()
-
- def tearDown(self):
- config.loadConfig(None)
- config.setDefaults(config_mod.defaultConfig)
- config.reload()
-
- 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()
-
- return service.services[0].args[1].protocolArgs['requestFactory']
-
-
-class CalDAVServiceMakerTests(BaseServiceMakerTests):
- """
- Test the service maker's behavior
- """
-
- def test_makeServiceDispatcher(self):
- """
- Test the default options of the dispatching makeService
- """
- validServices = ['Slave', 'Master', 'Combined']
-
- self.config['HTTPPort'] = 80
-
- for service in validServices:
- self.config['ProcessType'] = service
- self.writeConfig()
- self.makeService()
-
- self.config['ProcessType'] = 'Unknown Service'
- self.writeConfig()
- self.assertRaises(UsageError, self.makeService)
-
-
-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, tap.CalDAVService),
- "%s is not a tap.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 = ((internet.TCPServer, self.config['HTTPPort']),
- (internet.SSLServer, self.config['SSLPort']))
-
- configuredSubServices = [(s.__class__, s.args)
- for s in service.services]
-
- for serviceClass, serviceArgs in configuredSubServices:
- self.failUnless(
- serviceClass in (s[0] for s in expectedSubServices))
-
- self.assertEquals(serviceArgs[0],
- dict(expectedSubServices)[serviceClass])
-
- 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:
- 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:
- 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, CalDAVServiceMaker.rootResourceClass))
-
- def test_principalResource(self):
- """
- Test the principal resource
- """
- site = self.getSite()
- root = site.resource.resource.resource
-
- self.failUnless(isinstance(
- root.getChild('principals'),
- CalDAVServiceMaker.principalResourceClass))
-
- def test_calendarResource(self):
- """
- Test the calendar resource
- """
- site = self.getSite()
- root = site.resource.resource.resource
-
- self.failUnless(isinstance(
- root.getChild('calendars'),
- CalDAVServiceMaker.calendarResourceClass))
-
-
-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'] = self.mktemp()
-
- self.writeConfig()
-
- open(self.config['SudoersFile'], 'w').write(sudoersFile)
-
- site = self.getSite()
- principals = site.resource.resource.resource.getChild('principals')
- directory = principals.directory
-
- self.failUnless(self.config['SudoersFile'])
-
- sudoService = directory.serviceForRecordType(
- SudoDirectoryService.recordType_sudoers)
-
- self.assertEquals(sudoService.plistFile.path,
- os.path.abspath(self.config['SudoersFile']))
-
- 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))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081120/71173404/attachment-0001.html>
More information about the calendarserver-changes
mailing list