[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