[CalendarServer-changes] [4444] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Thu Jul 9 14:53:36 PDT 2009
Revision: 4444
http://trac.macosforge.org/projects/calendarserver/changeset/4444
Author: wsanchez at apple.com
Date: 2009-07-09 14:53:36 -0700 (Thu, 09 Jul 2009)
Log Message:
-----------
Merge config-separation branch: Allow for multiple configuration files
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/sidecar/task.py
CalendarServer/trunk/calendarserver/tap/caldav.py
CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
CalendarServer/trunk/calendarserver/tools/util.py
CalendarServer/trunk/twistedcaldav/config.py
CalendarServer/trunk/twistedcaldav/mail.py
CalendarServer/trunk/twistedcaldav/notify.py
CalendarServer/trunk/twistedcaldav/test/test_config.py
CalendarServer/trunk/twistedcaldav/test/test_notify.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/stdconfig.py
Property Changed:
----------------
CalendarServer/trunk/
CalendarServer/trunk/doc/Extensions/caldav-privatecomments.txt
CalendarServer/trunk/doc/Extensions/caldav-privatecomments.xml
CalendarServer/trunk/doc/Extensions/caldav-schedulingchanges.txt
CalendarServer/trunk/doc/Extensions/caldav-schedulingchanges.xml
Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
+ /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
Modified: CalendarServer/trunk/calendarserver/sidecar/task.py
===================================================================
--- CalendarServer/trunk/calendarserver/sidecar/task.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/calendarserver/sidecar/task.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -25,14 +25,15 @@
from time import sleep
from twisted.application.service import Service, IServiceMaker
from twisted.internet.address import IPv4Address
-from twisted.internet.defer import DeferredList, succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import DeferredList, inlineCallbacks, returnValue
from twisted.internet.reactor import callLater
from twisted.plugin import IPlugin
from twisted.python.reflect import namedClass
from twisted.python.usage import Options, UsageError
from twisted.web2.http_headers import Headers
from twistedcaldav import memcachepool
-from twistedcaldav.config import config, defaultConfig, defaultConfigFile
+from twistedcaldav.config import config
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
from twistedcaldav.ical import Component
from twistedcaldav.log import Logger, LoggingMixIn
@@ -247,7 +248,7 @@
class CalDAVTaskOptions(Options):
optParameters = [[
- "config", "f", defaultConfigFile, "Path to configuration file."
+ "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
]]
def __init__(self, *args, **kwargs):
@@ -315,7 +316,7 @@
if "=" in option:
path, value = option.split('=')
self._setOverride(
- defaultConfig,
+ DEFAULT_CONFIG,
path.split('/'),
value,
self.overrides
@@ -326,7 +327,7 @@
opt_o = opt_option
def postOptions(self):
- config.loadConfig(self['config'])
+ config.load(self['config'])
config.updateDefaults(self.overrides)
self.parent['pidfile'] = None
Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -39,7 +39,6 @@
from twisted.python.usage import Options, UsageError
from twisted.python.reflect import namedClass
from twisted.plugin import IPlugin
-from twisted.internet.defer import DeferredList, succeed, inlineCallbacks, returnValue
from twisted.internet.reactor import callLater
from twisted.internet.process import ProcessExitedAlready
from twisted.internet.protocol import Protocol, Factory
@@ -71,7 +70,8 @@
from twistedcaldav.accesslog import RotatingFileAccessLoggingObserver
from twistedcaldav.accesslog import AMPLoggingFactory
from twistedcaldav.accesslog import AMPCommonAccessLoggingObserver
-from twistedcaldav.config import config, defaultConfig, defaultConfigFile
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
+from twistedcaldav.config import config
from twistedcaldav.config import ConfigurationError
from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
from twistedcaldav.directory.digest import QopDigestCredentialFactory
@@ -134,7 +134,7 @@
class CalDAVOptions (Options, LoggingMixIn):
optParameters = [[
- "config", "f", defaultConfigFile, "Path to configuration file."
+ "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
]]
zsh_actions = {"config" : "_files -g '*.plist'"}
@@ -204,7 +204,7 @@
if "=" in option:
path, value = option.split("=")
self.setOverride(
- defaultConfig,
+ DEFAULT_CONFIG,
path.split("/"),
value,
self.overrides
@@ -215,6 +215,10 @@
opt_o = opt_option
def postOptions(self):
+ self.loadConfiguration()
+ self.checkConfiguration()
+
+ def loadConfiguration(self):
if not os.path.exists(self["config"]):
self.log_info("Config file %s not found, using defaults"
% (self["config"],))
@@ -223,13 +227,14 @@
% (self["config"],))
try:
- config.loadConfig(self["config"])
+ config.load(self["config"])
except ConfigurationError, e:
log.err("Invalid configuration: %s" % (e,))
sys.exit(1)
config.updateDefaults(self.overrides)
-
+
+ def checkConfiguration(self):
uid, gid = None, None
if self.parent["uid"] or self.parent["gid"]:
@@ -351,7 +356,14 @@
timezoneServiceResourceClass = TimezoneServiceFile
webCalendarResourceClass = WebCalendarResource
webAdminResourceClass = WebAdminResource
+
+ #
+ # Default tap names
+ #
+ mailGatewayTapName = "caldav_mailgateway"
+ notifierTapName = "caldav_notifier"
+
def makeService(self, options):
self.log_info("%s %s starting %s process..." % (self.description, version, config.ProcessType))
@@ -401,7 +413,10 @@
self.log_info("SIGHUP recieved at %s" % (location(frame),))
# Reload the config file
- config.reload()
+ try:
+ config.reload()
+ except ConfigurationError, e:
+ self.log_error("Invalid configuration: {0}".format(e))
# If combined service send signal to all caldavd children
if hasattr(service, "processMonitor"):
@@ -745,11 +760,11 @@
vary = True,
)
- def updateChannel(config, items):
- channel.maxRequests = config.MaxRequests
- channel.retryAfter = config.HTTPRetryAfter
+ def updateChannel(configDict):
+ channel.maxRequests = configDict.MaxRequests
+ channel.retryAfter = configDict.HTTPRetryAfter
- config.addHook(updateChannel)
+ config.addPostUpdateHook(updateChannel)
if not config.BindAddresses:
config.BindAddresses = [""]
@@ -1085,7 +1100,7 @@
notificationsArgv.extend(("-g", config.GroupName))
notificationsArgv.extend((
"--reactor=%s" % (config.Twisted.reactor,),
- "-n", "caldav_notifier",
+ "-n", self.notifierTapName,
"-f", options["config"],
))
monitor.addProcess("notifications", notificationsArgv,
@@ -1107,7 +1122,7 @@
mailGatewayArgv.extend(("-g", config.GroupName))
mailGatewayArgv.extend((
"--reactor=%s" % (config.Twisted.reactor,),
- "-n", "caldav_mailgateway",
+ "-n", self.mailGatewayTapName,
"-f", options["config"],
))
Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -27,7 +27,8 @@
from twext.python.plistlib import writePlist
-from twistedcaldav.config import config, ConfigDict, defaultConfig, _mergeData
+from twistedcaldav.config import config, ConfigDict, _mergeData
+from twistedcaldav.stdconfig import DEFAULT_CONFIG
from twistedcaldav.directory.aggregate import AggregateDirectoryService
from twistedcaldav.directory.sudo import SudoDirectoryService
@@ -70,8 +71,7 @@
self.config.parent["nodaemon"] = False
def tearDown(self):
- config.loadConfig(None)
- config.setDefaults(defaultConfig)
+ config.setDefaults(DEFAULT_CONFIG)
config.reload()
def test_overridesConfig(self):
@@ -124,7 +124,7 @@
Test that specifying a config file from the command line
loads the global config with those values properly.
"""
- myConfig = ConfigDict(defaultConfig)
+ myConfig = ConfigDict(DEFAULT_CONFIG)
myConfig.Authentication.Basic.Enabled = False
myConfig.MultiProcess.LoadBalancer.Enabled = False
@@ -177,7 +177,7 @@
self.options.parent["uid"] = None
self.options.parent["nodaemon"] = None
- self.config = ConfigDict(defaultConfig)
+ self.config = ConfigDict(DEFAULT_CONFIG)
accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml")
pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
@@ -206,9 +206,8 @@
self.writeConfig()
def tearDown(self):
- config.loadConfig(None)
- config.setDefaults(defaultConfig)
- config.reload()
+ config.setDefaults(DEFAULT_CONFIG)
+ config.reset()
def writeConfig(self):
"""
Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/calendarserver/tools/util.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -28,17 +28,18 @@
from twisted.python.reflect import namedClass
import socket
-from twistedcaldav.config import config, defaultConfigFile, ConfigurationError
+from twistedcaldav.config import config, ConfigurationError
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
def loadConfig(configFileName):
if configFileName is None:
- configFileName = defaultConfigFile
+ configFileName = DEFAULT_CONFIG_FILE
if not os.path.isfile(configFileName):
raise ConfigurationError("No config file: %s" % (configFileName,))
- config.loadConfig(configFileName)
+ config.load(configFileName)
return config
Property changes on: CalendarServer/trunk/doc/Extensions/caldav-privatecomments.txt
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-privatecomments-00.txt:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-privatecomments-00.txt:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-privatecomments.txt:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-privatecomments-00.txt:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-privatecomments.txt:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-privatecomments.txt:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-privatecomments.txt:4068-4075
+ /CalendarServer/branches/config-separation/doc/Extensions/caldav-privatecomments.txt:4379-4443
/CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-privatecomments-00.txt:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-privatecomments-00.txt:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-privatecomments.txt:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-privatecomments-00.txt:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-privatecomments.txt:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-privatecomments.txt:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-privatecomments.txt:4068-4075
Property changes on: CalendarServer/trunk/doc/Extensions/caldav-privatecomments.xml
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-privatecomments-00.xml:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-privatecomments-00.xml:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-privatecomments.xml:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-privatecomments-00.xml:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-privatecomments.xml:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-privatecomments.xml:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-privatecomments.xml:4068-4075
+ /CalendarServer/branches/config-separation/doc/Extensions/caldav-privatecomments.xml:4379-4443
/CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-privatecomments-00.xml:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-privatecomments-00.xml:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-privatecomments.xml:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-privatecomments-00.xml:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-privatecomments.xml:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-privatecomments.xml:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-privatecomments.xml:4068-4075
Property changes on: CalendarServer/trunk/doc/Extensions/caldav-schedulingchanges.txt
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-schedulingchanges-01.txt:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-schedulingchanges-01.txt:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-schedulingchanges.txt:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-schedulingchanges-01.txt:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-schedulingchanges.txt:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-schedulingchanges.txt:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-schedulingchanges.txt:4068-4075
+ /CalendarServer/branches/config-separation/doc/Extensions/caldav-schedulingchanges.txt:4379-4443
/CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-schedulingchanges-01.txt:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-schedulingchanges-01.txt:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-schedulingchanges.txt:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-schedulingchanges-01.txt:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-schedulingchanges.txt:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-schedulingchanges.txt:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-schedulingchanges.txt:4068-4075
Property changes on: CalendarServer/trunk/doc/Extensions/caldav-schedulingchanges.xml
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-schedulingchanges-01.xml:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-schedulingchanges-01.xml:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-schedulingchanges.xml:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-schedulingchanges-01.xml:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-schedulingchanges.xml:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-schedulingchanges.xml:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-schedulingchanges.xml:4068-4075
+ /CalendarServer/branches/config-separation/doc/Extensions/caldav-schedulingchanges.xml:4379-4443
/CalendarServer/branches/users/cdaboo/attendee-comments-2886/doc/Extensions/caldav-schedulingchanges-01.xml:2887-2910
/CalendarServer/branches/users/cdaboo/byebye-serviceslocator-2937/doc/Extensions/caldav-schedulingchanges-01.xml:2938-3097
/CalendarServer/branches/users/cdaboo/implicit-if-match-3306/doc/Extensions/caldav-schedulingchanges.xml:3307-3349
/CalendarServer/branches/users/cdaboo/implicitauto-2947/doc/Extensions/caldav-schedulingchanges-01.xml:2948-2989
/CalendarServer/branches/users/cdaboo/location-partial-accept-3573/doc/Extensions/caldav-schedulingchanges.xml:3574-3581
/CalendarServer/branches/users/sagen/resource-delegates-4038/doc/Extensions/caldav-schedulingchanges.xml:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/doc/Extensions/caldav-schedulingchanges.xml:4068-4075
Modified: CalendarServer/trunk/twistedcaldav/config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/config.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/twistedcaldav/config.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -15,31 +15,29 @@
##
__all__ = [
- "defaultConfigFile",
- "defaultConfig",
- "ConfigDict",
"Config",
+ "ConfigDict",
+ "ConfigProvider",
"ConfigurationError",
"config",
]
+import os
import copy
-import re
-from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import TwistedACLInheritable
+class ConfigurationError(RuntimeError):
+ """
+ Invalid server configuration.
+ """
-from twext.python.plistlib import readPlist
-
-from twistedcaldav.log import Logger
-from twistedcaldav.log import clearLogLevels, setLogLevelForNamespace, InvalidLogLevelError
-from twistedcaldav.util import (
- KeychainAccessError, KeychainPasswordNotFound, getPasswordFromKeychain
-)
-
-log = Logger()
-
-class ConfigDict (dict):
+class ConfigDict(dict):
+ """
+ Dictionary which can be accessed using attribute syntax, because
+ that reads an writes nicer in code. For example:
+ C{config.Thingo.Tiny.Tweak}
+ instead of:
+ C{config.["Thingo"]["Tiny"]["Tweak"]}
+ """
def __init__(self, mapping=None):
if mapping is not None:
for key, value in mapping.iteritems():
@@ -49,580 +47,82 @@
return "*" + dict.__repr__(self)
def __setitem__(self, key, value):
+ if key.startswith("_"):
+ # Names beginning with "_" are reserved for real attributes
+ raise KeyError("Keys may not begin with '_': %s" % (key,))
+
if isinstance(value, dict) and not isinstance(value, self.__class__):
dict.__setitem__(self, key, self.__class__(value))
else:
dict.__setitem__(self, key, value)
def __setattr__(self, attr, value):
- if attr[0] == "_":
+ if attr.startswith("_"):
dict.__setattr__(self, attr, value)
else:
self[attr] = value
def __getattr__(self, attr):
- if attr in self:
+ if not attr.startswith("_") and attr in self:
return self[attr]
else:
- return dict.__getattr__(self, attr)
+ return dict.__getattribute__(self, attr)
-defaultConfigFile = "/etc/caldavd/caldavd.plist"
-
-serviceDefaultParams = {
- "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
- "xmlFile": "/etc/caldavd/accounts.xml",
- },
- "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
- "node": "/Search",
- "restrictEnabledRecords": False,
- "restrictToGroup": "",
- "cacheTimeout": 30,
- },
-}
-
-defaultConfig = {
- # Note: Don't use None values below; that confuses the command-line parser.
-
- #
- # Public network address information
- #
- # This is the server's public network address, which is provided to
- # clients in URLs and the like. It may or may not be the network
- # address that the server is listening to directly, though it is by
- # default. For example, it may be the address of a load balancer or
- # proxy which forwards connections to the server.
- #
- "ServerHostName": "", # Network host name.
- "HTTPPort": 0, # HTTP port (0 to disable HTTP)
- "SSLPort" : 0, # SSL port (0 to disable HTTPS)
- "RedirectHTTPToHTTPS" : False, # If True, all nonSSL requests redirected to an SSL Port
- "SSLMethod" : "SSLv3_METHOD", # SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD
-
- #
- # Network address configuration information
- #
- # This configures the actual network address that the server binds to.
- #
- "BindAddresses": [], # List of IP addresses to bind to [empty = all]
- "BindHTTPPorts": [], # List of port numbers to bind to for HTTP [empty = same as "Port"]
- "BindSSLPorts" : [], # List of port numbers to bind to for SSL [empty = same as "SSLPort"]
-
- #
- # Data store
- #
- "DataRoot" : "/Library/CalendarServer/Data",
- "DocumentRoot" : "/Library/CalendarServer/Documents",
- "UserQuota" : 104857600, # User quota (in bytes)
- "MaximumAttachmentSize" : 1048576, # Attachment size limit (in bytes)
- "MaxAttendeesPerInstance" : 100, # Maximum number of unique attendees
- "MaxInstancesForRRULE" : 400, # Maximum number of instances for an RRULE
- "WebCalendarRoot" : "/usr/share/collaboration",
-
- "Aliases": {},
-
- #
- # Directory service
- #
- # A directory service provides information about principals (eg.
- # users, groups, locations and resources) to the server.
- #
- "DirectoryService": {
- "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
- "params": serviceDefaultParams["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
- },
-
- #
- # Special principals
- #
- "AdminPrincipals": [], # Principals with "DAV:all" access (relative URLs)
- "ReadPrincipals": [], # Principals with "DAV:read" access (relative URLs)
- "SudoersFile": "/etc/caldavd/sudoers.plist", # Principals that can pose as other principals
- "EnableProxyPrincipals": True, # Create "proxy access" principals
-
- #
- # Permissions
- #
- "EnableAnonymousReadRoot": True, # Allow unauthenticated read access to /
- "EnableAnonymousReadNav": False, # Allow unauthenticated read access to hierachcy
- "EnablePrincipalListings": True, # Allow listing of principal collections
- "EnableMonolithicCalendars": True, # Render calendar collections as a monolithic iCalendar object
-
- #
- # Client controls
- #
- "RejectClients": [], # List of regexes for clients to disallow
-
- #
- # Authentication
- #
- "Authentication": {
- "Basic": { "Enabled": False }, # Clear text; best avoided
- "Digest": { # Digest challenge/response
- "Enabled": True,
- "Algorithm": "md5",
- "Qop": "",
- },
- "Kerberos": { # Kerberos/SPNEGO
- "Enabled": False,
- "ServicePrincipal": ""
- },
- "Wiki": {
- "Enabled": False,
- "Cookie": "sessionID",
- "URL": "http://127.0.0.1:8086/RPC2",
- "UserMethod": "userForSession",
- "WikiMethod": "accessLevelForUserWikiCalendar",
- },
- },
-
- #
- # Logging
- #
- "AccessLogFile" : "/var/log/caldavd/access.log", # Apache-style access log
- "ErrorLogFile" : "/var/log/caldavd/error.log", # Server activity log
- "ServerStatsFile": "/var/run/caldavd/stats.plist",
- "PIDFile" : "/var/run/caldavd.pid",
- "RotateAccessLog" : False,
- "EnableExtendedAccessLog" : True,
- "DefaultLogLevel" : "",
- "LogLevels" : {},
-
- "AccountingCategories": {
- "iTIP": False,
- },
- "AccountingPrincipals": [],
- "AccountingLogRoot" : "/var/log/caldavd/accounting",
-
- "GlobalStatsSocket" : "/var/run/caldavd-stats.sock",
- "GlobalStatsLoggingPeriod" : 60,
- "GlobalStatsLoggingFrequency" : 12,
-
- #
- # SSL/TLS
- #
- "SSLCertificate" : "", # Public key
- "SSLPrivateKey" : "", # Private key
- "SSLAuthorityChain" : "", # Certificate Authority Chain
- "SSLPassPhraseDialog": "/etc/apache2/getsslpassphrase",
- "SSLCertAdmin" : "/usr/sbin/certadmin",
-
- #
- # Process management
- #
-
- # Username and Groupname to drop privileges to, if empty privileges will
- # not be dropped.
-
- "UserName": "",
- "GroupName": "",
- "ProcessType": "Combined",
- "MultiProcess": {
- "ProcessCount": 0,
- "MinProcessCount": 4,
- "LoadBalancer": {
- "Enabled": True,
- "Scheduler": "LeastConnections",
- },
- "StaggeredStartup": {
- "Enabled": False,
- "Interval": 15,
- },
- },
-
- #
- # Service ACLs
- #
- "EnableSACLs": False,
-
- #
- # Non-standard CalDAV extensions
- #
- "EnableDropBox" : False, # Calendar Drop Box
- "EnablePrivateEvents" : False, # Private Events
- "EnableTimezoneService" : False, # Timezone service
-
- #
- # Web-based administration
- #
- "EnableWebAdmin" : True,
-
- #
- # Scheduling related options
- #
-
- "Scheduling": {
-
- "CalDAV": {
- "EmailDomain" : "", # Domain for mailto calendar user addresses on this server
- "HTTPDomain" : "", # Domain for http calendar user addresses on this server
- "AddressPatterns" : [], # Reg-ex patterns to match local calendar user addresses
- "OldDraftCompatibility" : True, # Whether to maintain compatibility with non-implicit mode
- "ScheduleTagCompatibility" : True, # Whether to support older clients that do not use Schedule-Tag feature
- "EnablePrivateComments" : True, # Private comments from attendees to organizer
- },
-
- "iSchedule": {
- "Enabled" : False, # iSchedule protocol
- "AddressPatterns" : [], # Reg-ex patterns to match iSchedule-able calendar user addresses
- "Servers" : "/etc/caldavd/servertoserver.xml", # iSchedule server configurations
- },
-
- "iMIP": {
- "Enabled" : False, # Server-to-iMIP protocol
- "MailGatewayServer" : "localhost",
- "MailGatewayPort" : 62310,
- "Username" : "com.apple.calendarserver", # For account injecting replies
- "Password" : "", # For account injecting replies
- "Sending": {
- "Server" : "", # SMTP server to relay messages through
- "Port" : 587, # SMTP server port to relay messages through
- "Address" : "", # 'From' address for server
- "UseSSL" : True,
- "Username" : "", # For account sending mail
- "Password" : "", # For account sending mail
- },
- "Receiving": {
- "Server" : "", # Server to retrieve email messages from
- "Port" : 0, # Server port to retrieve email messages from
- "UseSSL" : True,
- "Type" : "", # Type of message access server: 'pop' or 'imap'
- "PollingSeconds" : 30, # How often to fetch mail
- "Username" : "", # For account receving mail
- "Password" : "", # For account receving mail
- },
- "AddressPatterns" : [], # Reg-ex patterns to match iMIP-able calendar user addresses
- "MailTemplatesDirectory": "/usr/share/caldavd/share/email_templates", # Directory containing HTML templates for email invitations (invite.html, cancel.html)
- "MailIconsDirectory": "/usr/share/caldavd/share/date_icons", # Directory containing language-specific subdirectories containing date-specific icons for email invitations
- "InvitationDaysToLive" : 90, # How many days invitations are valid
- },
-
- "Options" : {
- "AllowGroupAsOrganizer" : False, # Allow groups to be Organizers
- "AllowLocationAsOrganizer" : False, # Allow locations to be Organizers
- "AllowResourceAsOrganizer" : False, # Allow resources to be Organizers
- }
- },
-
- "FreeBusyURL": {
- "Enabled" : False, # Per-user free-busy-url protocol
- "TimePeriod" : 14, # Number of days into the future to generate f-b data if no explicit time-range is specified
- "AnonymousAccess" : False, # Allow anonymous read access to free-busy URL
- },
-
- #
- # Notifications
- #
- "Notifications" : {
- "Enabled": False,
- "CoalesceSeconds" : 3,
- "InternalNotificationHost" : "localhost",
- "InternalNotificationPort" : 62309,
- "BindAddress" : "127.0.0.1",
-
- "Services" : {
- "SimpleLineNotifier" : {
- "Service" : "twistedcaldav.notify.SimpleLineNotifierService",
- "Enabled" : False,
- "Port" : 62308,
- },
- "XMPPNotifier" : {
- "Service" : "twistedcaldav.notify.XMPPNotifierService",
- "Enabled" : False,
- "Host" : "", # "xmpp.host.name"
- "Port" : 5222,
- "JID" : "", # "jid at xmpp.host.name/resource"
- "Password" : "",
- "ServiceAddress" : "", # "pubsub.xmpp.host.name"
- "NodeConfiguration" : {
- "pubsub#deliver_payloads" : "1",
- "pubsub#persist_items" : "1",
- },
- "KeepAliveSeconds" : 120,
- "HeartbeatMinutes" : 30,
- "AllowedJIDs": [],
- },
- }
- },
-
- #
- # Performance tuning
- #
-
- # Set the maximum number of outstanding requests to this server.
- "MaxRequests": 600,
-
- "ListenBacklog": 50,
- "IdleConnectionTimeOut": 15,
- "UIDReservationTimeOut": 30 * 60,
-
-
- #
- # Localization
- #
- "Localization" : {
- "TranslationsDirectory" : "/usr/share/caldavd/share/translations",
- "LocalesDirectory" : "/usr/share/caldavd/share/locales",
- "Language" : "English",
- },
-
-
- #
- # Implementation details
- #
- # The following are specific to how the server is built, and useful
- # for development, but shouldn't be needed by users.
- #
-
- # Twisted
- "Twisted": {
- "twistd": "/usr/share/caldavd/bin/twistd",
- "reactor": "select",
- },
-
- # Python Director
- "PythonDirector": {
- "pydir": "/usr/share/caldavd/bin/pydir.py",
- "ConfigFile": "/etc/caldavd/pydir.xml",
- "ControlSocket": "/var/run/caldavd-pydir.sock",
- },
-
- # Umask
- "umask": 0027,
-
- # A TCP port used for communication between the child and master
- # processes (bound to 127.0.0.1). Specify 0 to let OS assign a port.
- "ControlPort": 0,
-
- # A unix socket used for communication between the child and master
- # processes. If blank, then an AF_INET socket is used instead.
- "ControlSocket": "/var/run/caldavd.sock",
-
-
- # Support for Content-Encoding compression options as specified in
- # RFC2616 Section 3.5
- "ResponseCompression": True,
-
- # The retry-after value (in seconds) to return with a 503 error
- "HTTPRetryAfter": 180,
-
- # Profiling options
- "Profiling": {
- "Enabled": False,
- "BaseDirectory": "/tmp/stats",
- },
-
- "Memcached": {
- "MaxClients": 5,
- "ClientEnabled": True,
- "ServerEnabled": True,
- "BindAddress": "127.0.0.1",
- "Port": 11211,
- "memcached": "memcached", # Find in PATH
- "MaxMemory": 0, # Megabytes
- "Options": [],
- },
-
- "EnableKeepAlive": True,
-
- "ResponseCacheTimeout": 30, # Minutes
-}
-
-class Config (object):
- """
- @DynamicAttrs
- """
- def __init__(self, defaults):
- if not isinstance(defaults, ConfigDict):
- defaults = ConfigDict(defaults)
-
- self.setDefaults(defaults)
- self._data = copy.deepcopy(defaults)
- self._configFile = None
- self._hooks = [
- self.updateHostName,
- self.updateDirectoryService,
- self.updateACLs,
- self.updateRejectClients,
- self.updateDropBox,
- self.updateLogLevels,
- self.updateNotifications,
- self.updateScheduling,
- ]
-
- def __str__(self):
- return str(self._data)
-
- def addHook(self, hook):
- self._hooks.append(hook)
-
- def update(self, items):
- if not isinstance(items, ConfigDict):
- items = ConfigDict(items)
-
- #
- # Call hooks
- #
- for hook in self._hooks:
- hook(self, items)
-
- @staticmethod
- def updateHostName(self, items):
- if not self.ServerHostName:
- from socket import getfqdn
- hostname = getfqdn()
- if not hostname:
- hostname = "localhost"
- self.ServerHostName = hostname
-
- @staticmethod
- def updateDirectoryService(self, items):
- #
- # Special handling for directory services configs
- #
- dsType = items.get("DirectoryService", {}).get("type", None)
- if dsType is None:
- dsType = self._data.DirectoryService.type
+ def __delattr__(self, attr):
+ if not attr.startswith("_") and attr in self:
+ del self[attr]
else:
- if dsType == self._data.DirectoryService.type:
- oldParams = self._data.DirectoryService.params
- newParams = items.DirectoryService.get("params", {})
- _mergeData(oldParams, newParams)
- else:
- if dsType in serviceDefaultParams:
- self._data.DirectoryService.params = copy.deepcopy(serviceDefaultParams[dsType])
- else:
- self._data.DirectoryService.params = {}
+ dict.__delattr__(self, attr)
- for param in items.get("DirectoryService", {}).get("params", {}):
- if dsType in serviceDefaultParams and param not in serviceDefaultParams[dsType]:
- log.warn("Parameter %s is not supported by service %s" % (param, dsType))
+class ConfigProvider(object):
+ """Configuration provider, abstraction for config storage/format/defaults"""
- _mergeData(self._data, items)
-
- if self._data.DirectoryService.type in serviceDefaultParams:
- for param in tuple(self._data.DirectoryService.params):
- if param not in serviceDefaultParams[self._data.DirectoryService.type]:
- del self._data.DirectoryService.params[param]
-
- @staticmethod
- def updateACLs(self, items):
- #
- # Base resource ACLs
- #
- def readOnlyACE(allowAnonymous):
- if allowAnonymous:
- reader = davxml.All()
- else:
- reader = davxml.Authenticated()
-
- return davxml.ACE(
- davxml.Principal(reader),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- ),
- davxml.Protected(),
- )
-
- self.AdminACEs = tuple(
- davxml.ACE(
- davxml.Principal(davxml.HRef(principal)),
- davxml.Grant(davxml.Privilege(davxml.All())),
- davxml.Protected(),
- TwistedACLInheritable(),
- )
- for principal in config.AdminPrincipals
- )
-
- self.ReadACEs = tuple(
- davxml.ACE(
- davxml.Principal(davxml.HRef(principal)),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- ),
- davxml.Protected(),
- TwistedACLInheritable(),
- )
- for principal in config.ReadPrincipals
- )
-
- self.RootResourceACL = davxml.ACL(
- # Read-only for anon or authenticated, depending on config
- readOnlyACE(self.EnableAnonymousReadRoot),
-
- # Add inheritable all access for admins
- *self.AdminACEs
- )
-
- log.debug("Root ACL: %s" % (self.RootResourceACL.toxml(),))
-
- self.ProvisioningResourceACL = davxml.ACL(
- # Read-only for anon or authenticated, depending on config
- readOnlyACE(self.EnableAnonymousReadNav),
-
- # Add read and read-acl access for admins
- *[
- davxml.ACE(
- davxml.Principal(davxml.HRef(principal)),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadACL()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- ),
- davxml.Protected(),
- )
- for principal in config.AdminPrincipals
- ]
- )
-
- log.debug("Nav ACL: %s" % (self.ProvisioningResourceACL.toxml(),))
-
- @staticmethod
- def updateRejectClients(self, items):
- #
- # Compile RejectClients expressions for speed
- #
- try:
- self.RejectClients = [re.compile(x) for x in self.RejectClients if x]
- except re.error, e:
- raise ConfigurationError("Invalid regular expression in RejectClients: %s" % (e,))
-
- @staticmethod
- def updateDropBox(self, items):
- #
- # FIXME: Use the config object instead of doing this here
- #
- from twistedcaldav.resource import CalendarPrincipalResource
- CalendarPrincipalResource.enableDropBox(self.EnableDropBox)
-
- @staticmethod
- def updateLogLevels(self, items):
- clearLogLevels()
-
- try:
- if "DefaultLogLevel" in self._data:
- level = self._data["DefaultLogLevel"]
- if not level:
- level = "warn"
- setLogLevelForNamespace(None, level)
-
- if "LogLevels" in self._data:
- for namespace in self._data["LogLevels"]:
- setLogLevelForNamespace(namespace, self._data["LogLevels"][namespace])
-
- except InvalidLogLevelError, e:
- raise ConfigurationError("Invalid log level: %s" % (e.level))
-
- def updateDefaults(self, items):
- _mergeData(self._defaults, items)
- self.update(items)
-
+ def __init__(self, defaults=None):
+ """Create configuration provider with given defaults"""
+ self._configFileName = None
+ if defaults is None:
+ self._defaults = ConfigDict()
+ else:
+ self._defaults = ConfigDict(copy.deepcopy(defaults))
+
+ def getDefaults(self):
+ """Return defaults"""
+ return self._defaults
+
def setDefaults(self, defaults):
- if not isinstance(defaults, ConfigDict):
- defaults = ConfigDict(defaults)
- self._defaults = copy.deepcopy(defaults)
+ """Change defaults"""
+ self._defaults = ConfigDict(copy.deepcopy(defaults))
+
+ def getConfigFileName(self):
+ """Return current configuration file path+name"""
+ return self._configFileName
+
+ def setConfigFileName(self, configFileName):
+ """Change configuration file path+name for next load operations"""
+ self._configFileName = configFileName
+ if self._configFileName:
+ self._configFileName = os.path.abspath(configFileName)
+
+ def hasErrors(self):
+ """Return true if last load operation encountered any errors"""
+ return False
+
+ def loadConfig(self):
+ """Load the configuration, return a dictionary of settings"""
+ return self._defaults
+
+class Config(object):
+
+ def __init__(self, provider=None):
+ if not provider:
+ self._provider = ConfigProvider()
+ else:
+ self._provider = provider
+ self._preUpdateHooks = set()
+ self._postUpdateHooks = set()
+ self.reset()
+
def __setattr__(self, attr, value):
if "_data" in self.__dict__ and attr in self.__dict__["_data"]:
self._data[attr] = value
@@ -632,172 +132,101 @@
def __getattr__(self, attr):
if attr in self._data:
return self._data[attr]
-
raise AttributeError(attr)
- def reload(self):
- self._reloading = True
- log.info("Reloading configuration from file: %s" % (self._configFile,))
- self._data = copy.deepcopy(self._defaults)
- self.loadConfig(self._configFile)
+ def __hasattr__(self, attr):
+ return attr in self._data
+
+ def __str__(self):
+ return str(self._data)
- def loadConfig(self, configFile):
- self._configFile = configFile
+ def get(self, attr, defaultValue):
+ parts = attr.split(".")
+ lastDict = self._data
+ for part in parts[:-1]:
+ if not part in lastDict:
+ lastDict[attr] = ConfigDict()
+ lastDict = lastDict.__getattr__(part)
+ configItem = parts[-1]
+ if configItem in lastDict:
+ return lastDict[configItem]
+ else:
+ lastDict[configItem] = defaultValue
+ return defaultValue
- if configFile:
- try:
- configDict = readPlist(configFile)
- except (IOError, OSError):
- log.error("Unable to open config file: %s" % (configFile,))
- else:
- configDict = _cleanup(configDict)
- self.update(ConfigDict(configDict))
+ def getInt(self, attr, defaultValue):
+ return int(self.get(attr, defaultValue))
- @staticmethod
- def updateNotifications(self, items):
- #
- # Notifications
- #
-
- # Reloading not supported -- requires process running as root
- if getattr(self, "_reloading", False):
- return
-
- for key, service in self.Notifications["Services"].iteritems():
- if service["Enabled"]:
- self.Notifications["Enabled"] = True
- break
+ def addPreUpdateHook(self, hook):
+ if isinstance(hook, list) or isinstance(hook, tuple):
+ self._preUpdateHooks.update(hook)
else:
- self.Notifications["Enabled"] = False
+ self._preUpdateHooks.add(hook)
+
+ def addPostUpdateHook(self, hook):
+ if isinstance(hook, list) or isinstance(hook, tuple):
+ self._postUpdateHooks.update(hook)
+ else:
+ self._postUpdateHooks.add(hook)
- for key, service in self.Notifications["Services"].iteritems():
- if (
- service["Service"] == "twistedcaldav.notify.XMPPNotifierService" and
- service["Enabled"]
- ):
- # Get password from keychain. If not there, fall back to what
- # is in the plist.
- try:
- password = getPasswordFromKeychain(service["JID"])
- service["Password"] = password
- log.info("XMPP password successfully retreived from keychain")
- except KeychainAccessError:
- # The system doesn't support keychain
- pass
- except KeychainPasswordNotFound:
- # The password doesn't exist in the keychain.
- log.error("XMPP password not found in keychain")
+ def getProvider(self):
+ return self._provider
+
+ def setProvider(self, provider):
+ self._provider = provider
+ self.reset()
- # Check for empty fields
- for key, value in service.iteritems():
- if not value and key not in ("AllowedJIDs", "HeartbeatMinutes", "Password"):
- raise ConfigurationError("Invalid %s for XMPPNotifierService: %r"
- % (key, value))
+ def setDefaults(self, defaults):
+ self._provider.setDefaults(defaults)
+ self.reset()
- @staticmethod
- def updateScheduling(self, items):
- #
- # Scheduling
- #
+ def updateDefaults(self, items):
+ _mergeData(self._provider.getDefaults(), items)
+ self.update(items)
- # Reloading not supported -- requires process running as root
- if getattr(self, "_reloading", False):
- return
+ def update(self, items):
+ if not isinstance(items, ConfigDict):
+ items = ConfigDict(items)
+ # Call hooks
+ for hook in self._preUpdateHooks:
+ hook(self._data, items)
+ _mergeData(self._data, items)
+ for hook in self._postUpdateHooks:
+ hook(self._data)
- service = self.Scheduling["iMIP"]
+ def load(self, configFile):
+ self._provider.setConfigFileName(configFile)
+ configDict = ConfigDict(self._provider.loadConfig())
+ if not self._provider.hasErrors():
+ self.update(configDict)
+ else:
+ raise ConfigurationError("Invalid configuration in %s"
+ % (self._provider.getConfigFileName(), ))
- if service["Enabled"]:
+ def reload(self):
+ configDict = ConfigDict(self._provider.loadConfig())
+ configDict._reloading = True
+ if not self._provider.hasErrors():
+ self.reset()
+ self.update(configDict)
+ else:
+ raise ConfigurationError("Invalid configuration in %s"
+ % (self._provider.getConfigFileName(), ))
- # Get password for the user that is allowed to inject iMIP replies
- # to the server's /inbox; if not available, fall back to plist
- if service["Username"]:
- try:
- service["Password"] = getPasswordFromKeychain(service["Username"])
- except KeychainAccessError:
- # The system doesn't support keychain
- pass
- except KeychainPasswordNotFound:
- # The password doesn't exist in the keychain.
- log.info("iMIP injecting password not found in keychain")
+ def reset(self):
+ self._data = ConfigDict(copy.deepcopy(self._provider.getDefaults()))
- for direction in ("Sending", "Receiving"):
- if service[direction].Username:
- # Get password from keychain. If not there, fall back to
- # what is in the plist.
- try:
- account = "%s@%s" % (
- service[direction].Username,
- service[direction].Server
- )
- password = getPasswordFromKeychain(account)
- service[direction]["Password"] = password
- log.info("iMIP %s password successfully retreived from keychain" % (direction,))
- except KeychainAccessError:
- # The system doesn't support keychain
- pass
- except KeychainPasswordNotFound:
- # The password doesn't exist in the keychain.
- log.info("iMIP %s password not found in keychain" %
- (direction,))
-
def _mergeData(oldData, newData):
for key, value in newData.iteritems():
if isinstance(value, (dict,)):
if key in oldData:
- assert isinstance(oldData[key], ConfigDict), "%r in %r is not a ConfigDict" % (oldData[key], oldData)
+ assert isinstance(oldData[key], ConfigDict), \
+ "%r in %r is not a ConfigDict" % (oldData[key], oldData)
else:
oldData[key] = {}
_mergeData(oldData[key], value)
else:
oldData[key] = value
-def _cleanup(configDict):
- cleanDict = copy.deepcopy(configDict)
-
- def unknown(key):
- log.err("Ignoring unknown configuration option: %r" % (key,))
- del cleanDict[key]
-
- def deprecated(oldKey, newKey):
- log.err("Configuration option %r is deprecated in favor of %r." % (oldKey, newKey))
- if oldKey in configDict and newKey in configDict:
- raise ConfigurationError(
- "Both %r and %r options are specified; use the %r option only."
- % (oldKey, newKey, newKey)
- )
-
- def renamed(oldKey, newKey):
- deprecated(oldKey, newKey)
- cleanDict[newKey] = configDict[oldKey]
- del cleanDict[oldKey]
-
- renamedOptions = {
-# "BindAddress": "BindAddresses",
- }
-
- for key in configDict:
- if key in defaultConfig:
- continue
-
- elif key in renamedOptions:
- renamed(key, renamedOptions[key])
-
-# elif key == "pydirConfig":
-# deprecated(key, "PythonDirector -> pydir")
-# if "PythonDirector" not in cleanDict:
-# cleanDict["PythonDirector"] = {}
-# cleanDict["PythonDirector"]["ConfigFile"] = cleanDict["pydirConfig"]
-# del cleanDict["pydirConfig"]
-
- else:
- unknown(key,)
-
- return cleanDict
-
-class ConfigurationError (RuntimeError):
- """
- Invalid server configuration.
- """
-
-config = Config(defaultConfig)
+config = Config()
Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/twistedcaldav/mail.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -47,7 +47,7 @@
from twistedcaldav import ical, caldavxml
from twistedcaldav import memcachepool
-from twistedcaldav.config import config, defaultConfig, defaultConfigFile
+from twistedcaldav.config import config
from twistedcaldav.directory.digest import QopDigestCredentialFactory
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
from twistedcaldav.directory.util import NotFilePath
@@ -59,6 +59,9 @@
from twistedcaldav.sql import AbstractSQLDatabase
from twistedcaldav.static import CalDAVFile, deliverSchedulePrivilegeSet
from twistedcaldav.util import AuthorizedHTTPGetter
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.localization import translationTo
from zope.interface import implements
@@ -90,7 +93,7 @@
class MailGatewayOptions(Options):
optParameters = [[
- "config", "f", defaultConfigFile, "Path to configuration file."
+ "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
]]
def __init__(self, *args, **kwargs):
@@ -158,7 +161,7 @@
if "=" in option:
path, value = option.split('=')
self._setOverride(
- defaultConfig,
+ DEFAULT_CONFIG,
path.split('/'),
value,
self.overrides
@@ -169,7 +172,7 @@
opt_o = opt_option
def postOptions(self):
- config.loadConfig(self['config'])
+ config.load(self['config'])
config.updateDefaults(self.overrides)
self.parent['pidfile'] = None
Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/twistedcaldav/notify.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -49,8 +49,9 @@
from twisted.words.protocols.jabber.xmlstream import IQ
from twisted.words.xish import domish
from twistedcaldav.log import LoggingMixIn
-from twistedcaldav.config import config, defaultConfig, defaultConfigFile
+from twistedcaldav.config import config
from twistedcaldav.memcacher import Memcacher
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
from twistedcaldav import memcachepool
from zope.interface import Interface, implements
from fnmatch import fnmatch
@@ -1199,7 +1200,7 @@
class NotificationOptions(Options):
optParameters = [[
- "config", "f", defaultConfigFile, "Path to configuration file."
+ "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
]]
def __init__(self, *args, **kwargs):
@@ -1267,7 +1268,7 @@
if "=" in option:
path, value = option.split('=')
self._setOverride(
- defaultConfig,
+ DEFAULT_CONFIG,
path.split('/'),
value,
self.overrides
@@ -1278,7 +1279,7 @@
opt_o = opt_option
def postOptions(self):
- config.loadConfig(self['config'])
+ config.load(self['config'])
config.updateDefaults(self.overrides)
self.parent['pidfile'] = None
Copied: CalendarServer/trunk/twistedcaldav/stdconfig.py (from rev 4443, CalendarServer/branches/config-separation/twistedcaldav/stdconfig.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -0,0 +1,698 @@
+##
+# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import os
+import copy
+import re
+
+from twisted.web2.dav import davxml
+from twisted.web2.dav.resource import TwistedACLInheritable
+
+from twext.python.plistlib import readPlist
+
+from twistedcaldav.config import (
+ ConfigProvider, ConfigurationError, config, _mergeData, )
+from twistedcaldav.log import (
+ Logger, clearLogLevels, setLogLevelForNamespace, InvalidLogLevelError, )
+from twistedcaldav.util import (
+ KeychainAccessError, KeychainPasswordNotFound, getPasswordFromKeychain, )
+
+log = Logger()
+
+DEFAULT_CONFIG_FILE = "/etc/caldavd/caldavd.plist"
+
+DEFAULT_SERVICE_PARAMS = {
+ "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
+ "xmlFile": "/etc/caldavd/accounts.xml",
+ },
+ "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
+ "node": "/Search",
+ "restrictEnabledRecords": False,
+ "restrictToGroup": "",
+ "cacheTimeout": 30,
+ },
+}
+
+DEFAULT_CONFIG = {
+ # Note: Don't use None values below; that confuses the command-line parser.
+
+ #
+ # Public network address information
+ #
+ # This is the server's public network address, which is provided to
+ # clients in URLs and the like. It may or may not be the network
+ # address that the server is listening to directly, though it is by
+ # default. For example, it may be the address of a load balancer or
+ # proxy which forwards connections to the server.
+ #
+ "ServerHostName": "", # Network host name.
+ "HTTPPort": 0, # HTTP port (0 to disable HTTP)
+ "SSLPort" : 0, # SSL port (0 to disable HTTPS)
+ "RedirectHTTPToHTTPS" : False, # If True, all nonSSL requests redirected to an SSL Port
+ "SSLMethod" : "SSLv3_METHOD", # SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD
+
+ #
+ # Network address configuration information
+ #
+ # This configures the actual network address that the server binds to.
+ #
+ "BindAddresses": [], # List of IP addresses to bind to [empty = all]
+ "BindHTTPPorts": [], # List of port numbers to bind to for HTTP [empty = same as "Port"]
+ "BindSSLPorts" : [], # List of port numbers to bind to for SSL [empty = same as "SSLPort"]
+
+ #
+ # Data store
+ #
+ "DataRoot" : "/Library/CalendarServer/Data",
+ "DocumentRoot" : "/Library/CalendarServer/Documents",
+ "UserQuota" : 104857600, # User quota (in bytes)
+ "MaximumAttachmentSize" : 1048576, # Attachment size limit (in bytes)
+ "MaxAttendeesPerInstance" : 100, # Maximum number of unique attendees
+ "MaxInstancesForRRULE" : 400, # Maximum number of instances for an RRULE
+ "WebCalendarRoot" : "/usr/share/collaboration",
+
+ "Aliases": {},
+
+ #
+ # Directory service
+ #
+ # A directory service provides information about principals (eg.
+ # users, groups, locations and resources) to the server.
+ #
+ "DirectoryService": {
+ "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
+ "params": DEFAULT_SERVICE_PARAMS["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
+ },
+
+ #
+ # Special principals
+ #
+ "AdminPrincipals": [], # Principals with "DAV:all" access (relative URLs)
+ "ReadPrincipals": [], # Principals with "DAV:read" access (relative URLs)
+ "SudoersFile": "/etc/caldavd/sudoers.plist", # Principals that can pose as other principals
+ "EnableProxyPrincipals": True, # Create "proxy access" principals
+
+ #
+ # Permissions
+ #
+ "EnableAnonymousReadRoot": True, # Allow unauthenticated read access to /
+ "EnableAnonymousReadNav": False, # Allow unauthenticated read access to hierachcy
+ "EnablePrincipalListings": True, # Allow listing of principal collections
+ "EnableMonolithicCalendars": True, # Render calendar collections as a monolithic iCalendar object
+
+ #
+ # Client controls
+ #
+ "RejectClients": [], # List of regexes for clients to disallow
+
+ #
+ # Authentication
+ #
+ "Authentication": {
+ "Basic": { "Enabled": False }, # Clear text; best avoided
+ "Digest": { # Digest challenge/response
+ "Enabled": True,
+ "Algorithm": "md5",
+ "Qop": "",
+ },
+ "Kerberos": { # Kerberos/SPNEGO
+ "Enabled": False,
+ "ServicePrincipal": ""
+ },
+ "Wiki": {
+ "Enabled": False,
+ "Cookie": "sessionID",
+ "URL": "http://127.0.0.1:8086/RPC2",
+ "UserMethod": "userForSession",
+ "WikiMethod": "accessLevelForUserWikiCalendar",
+ },
+ },
+
+ #
+ # Logging
+ #
+ "AccessLogFile" : "/var/log/caldavd/access.log", # Apache-style access log
+ "ErrorLogFile" : "/var/log/caldavd/error.log", # Server activity log
+ "ServerStatsFile": "/var/run/caldavd/stats.plist",
+ "PIDFile" : "/var/run/caldavd.pid",
+ "RotateAccessLog" : False,
+ "EnableExtendedAccessLog": True,
+ "DefaultLogLevel" : "",
+ "LogLevels" : {},
+
+ "AccountingCategories": {
+ "iTIP": False,
+ },
+ "AccountingPrincipals": [],
+ "AccountingLogRoot" : "/var/log/caldavd/accounting",
+
+ "GlobalStatsSocket" : "/var/run/caldavd-stats.sock",
+ "GlobalStatsLoggingPeriod" : 60,
+ "GlobalStatsLoggingFrequency" : 12,
+
+ #
+ # SSL/TLS
+ #
+ "SSLCertificate" : "", # Public key
+ "SSLPrivateKey" : "", # Private key
+ "SSLAuthorityChain" : "", # Certificate Authority Chain
+ "SSLPassPhraseDialog": "/etc/apache2/getsslpassphrase",
+ "SSLCertAdmin" : "/usr/sbin/certadmin",
+
+ #
+ # Process management
+ #
+
+ # Username and Groupname to drop privileges to, if empty privileges will
+ # not be dropped.
+
+ "UserName": "",
+ "GroupName": "",
+ "ProcessType": "Combined",
+ "MultiProcess": {
+ "ProcessCount": 0,
+ "MinProcessCount": 4,
+ "LoadBalancer": {
+ "Enabled": True,
+ "Scheduler": "LeastConnections",
+ },
+ "StaggeredStartup": {
+ "Enabled": False,
+ "Interval": 15,
+ },
+ },
+
+ #
+ # Service ACLs
+ #
+ "EnableSACLs": False,
+
+ #
+ # Non-standard CalDAV extensions
+ #
+ "EnableDropBox" : False, # Calendar Drop Box
+ "EnablePrivateEvents" : False, # Private Events
+ "EnableTimezoneService" : False, # Timezone service
+
+ #
+ # Web-based administration
+ #
+ "EnableWebAdmin" : True,
+
+ #
+ # Scheduling related options
+ #
+
+ "Scheduling": {
+
+ "CalDAV": {
+ "EmailDomain" : "", # Domain for mailto calendar user addresses on this server
+ "HTTPDomain" : "", # Domain for http calendar user addresses on this server
+ "AddressPatterns" : [], # Reg-ex patterns to match local calendar user addresses
+ "OldDraftCompatibility" : True, # Whether to maintain compatibility with non-implicit mode
+ "ScheduleTagCompatibility" : True, # Whether to support older clients that do not use Schedule-Tag feature
+ "EnablePrivateComments" : True, # Private comments from attendees to organizer
+ },
+
+ "iSchedule": {
+ "Enabled" : False, # iSchedule protocol
+ "AddressPatterns" : [], # Reg-ex patterns to match iSchedule-able calendar user addresses
+ "Servers" : "/etc/caldavd/servertoserver.xml", # iSchedule server configurations
+ },
+
+ "iMIP": {
+ "Enabled" : False, # Server-to-iMIP protocol
+ "MailGatewayServer" : "localhost",
+ "MailGatewayPort" : 62310,
+ "Username" : "com.apple.calendarserver", # For account injecting replies
+ "Password" : "", # For account injecting replies
+ "Sending": {
+ "Server" : "", # SMTP server to relay messages through
+ "Port" : 587, # SMTP server port to relay messages through
+ "Address" : "", # 'From' address for server
+ "UseSSL" : True,
+ "Username" : "", # For account sending mail
+ "Password" : "", # For account sending mail
+ },
+ "Receiving": {
+ "Server" : "", # Server to retrieve email messages from
+ "Port" : 0, # Server port to retrieve email messages from
+ "UseSSL" : True,
+ "Type" : "", # Type of message access server: 'pop' or 'imap'
+ "PollingSeconds" : 30, # How often to fetch mail
+ "Username" : "", # For account receving mail
+ "Password" : "", # For account receving mail
+ },
+ "AddressPatterns" : [], # Reg-ex patterns to match iMIP-able calendar user addresses
+ "MailTemplatesDirectory": "/usr/share/caldavd/share/email_templates", # Directory containing HTML templates for email invitations (invite.html, cancel.html)
+ "MailIconsDirectory": "/usr/share/caldavd/share/date_icons", # Directory containing language-specific subdirectories containing date-specific icons for email invitations
+ "InvitationDaysToLive" : 90, # How many days invitations are valid
+ },
+
+ "Options" : {
+ "AllowGroupAsOrganizer" : False, # Allow groups to be Organizers
+ "AllowLocationAsOrganizer" : False, # Allow locations to be Organizers
+ "AllowResourceAsOrganizer" : False, # Allow resources to be Organizers
+ }
+ },
+
+ "FreeBusyURL": {
+ "Enabled" : False, # Per-user free-busy-url protocol
+ "TimePeriod" : 14, # Number of days into the future to generate f-b data if no explicit time-range is specified
+ "AnonymousAccess" : False, # Allow anonymous read access to free-busy URL
+ },
+
+ #
+ # Notifications
+ #
+ "Notifications" : {
+ "Enabled": False,
+ "CoalesceSeconds" : 3,
+ "InternalNotificationHost" : "localhost",
+ "InternalNotificationPort" : 62309,
+ "BindAddress" : "127.0.0.1",
+
+ "Services" : {
+ "SimpleLineNotifier" : {
+ "Service" : "twistedcaldav.notify.SimpleLineNotifierService",
+ "Enabled" : False,
+ "Port" : 62308,
+ },
+ "XMPPNotifier" : {
+ "Service" : "twistedcaldav.notify.XMPPNotifierService",
+ "Enabled" : False,
+ "Host" : "", # "xmpp.host.name"
+ "Port" : 5222,
+ "JID" : "", # "jid at xmpp.host.name/resource"
+ "Password" : "",
+ "ServiceAddress" : "", # "pubsub.xmpp.host.name"
+ "NodeConfiguration" : {
+ "pubsub#deliver_payloads" : "1",
+ "pubsub#persist_items" : "1",
+ },
+ "KeepAliveSeconds" : 120,
+ "HeartbeatMinutes" : 30,
+ "AllowedJIDs": [],
+ },
+ }
+ },
+
+ #
+ # Performance tuning
+ #
+
+ # Set the maximum number of outstanding requests to this server.
+ "MaxRequests": 600,
+
+ "ListenBacklog": 50,
+ "IdleConnectionTimeOut": 15,
+ "UIDReservationTimeOut": 30 * 60,
+
+
+ #
+ # Localization
+ #
+ "Localization" : {
+ "TranslationsDirectory" : "/usr/share/caldavd/share/translations",
+ "LocalesDirectory" : "/usr/share/caldavd/share/locales",
+ "Language" : "English",
+ },
+
+
+ #
+ # Implementation details
+ #
+ # The following are specific to how the server is built, and useful
+ # for development, but shouldn't be needed by users.
+ #
+
+ # Twisted
+ "Twisted": {
+ "twistd": "/usr/share/caldavd/bin/twistd",
+ "reactor": "select",
+ },
+
+ # Python Director
+ "PythonDirector": {
+ "pydir": "/usr/share/caldavd/bin/pydir.py",
+ "ConfigFile": "/etc/caldavd/pydir.xml",
+ "ControlSocket": "/var/run/caldavd-pydir.sock",
+ },
+
+ # Umask
+ "umask": 0027,
+
+ # A TCP port used for communication between the child and master
+ # processes (bound to 127.0.0.1). Specify 0 to let OS assign a port.
+ "ControlPort": 0,
+
+ # A unix socket used for communication between the child and master
+ # processes. If blank, then an AF_INET socket is used instead.
+ "ControlSocket": "/var/run/caldavd.sock",
+
+
+ # Support for Content-Encoding compression options as specified in
+ # RFC2616 Section 3.5
+ "ResponseCompression": True,
+
+ # The retry-after value (in seconds) to return with a 503 error
+ "HTTPRetryAfter": 180,
+
+ # Profiling options
+ "Profiling": {
+ "Enabled": False,
+ "BaseDirectory": "/tmp/stats",
+ },
+
+ "Memcached": {
+ "MaxClients": 5,
+ "ClientEnabled": True,
+ "ServerEnabled": True,
+ "BindAddress": "127.0.0.1",
+ "Port": 11211,
+ "memcached": "memcached", # Find in PATH
+ "MaxMemory": 0, # Megabytes
+ "Options": [],
+ },
+
+ "EnableKeepAlive": True,
+ "ResponseCacheTimeout": 30, # Minutes
+}
+
+class PListConfigProvider(ConfigProvider):
+
+ def loadConfig(self):
+ configDict = {}
+ if self._configFileName:
+ try:
+ configDict = readPlist(self._configFileName)
+ except (IOError, OSError):
+ log.error("Configuration file does not exist or is inaccessible: %s" % (self._configFileName,))
+ else:
+ configDict = _cleanup(configDict, self._defaults)
+ return configDict
+
+
+def _updateHostName(configDict):
+ if not configDict.ServerHostName:
+ from socket import getfqdn
+ hostname = getfqdn()
+ if not hostname:
+ hostname = "localhost"
+ configDict.ServerHostName = hostname
+
+def _preUpdateDirectoryService(configDict, items):
+ # Special handling for directory services configs
+ dsType = items.get("DirectoryService", {}).get("type", None)
+ if dsType is None:
+ dsType = configDict.DirectoryService.type
+ else:
+ if dsType == configDict.DirectoryService.type:
+ oldParams = configDict.DirectoryService.params
+ newParams = items.DirectoryService.get("params", {})
+ _mergeData(oldParams, newParams)
+ else:
+ if dsType in DEFAULT_SERVICE_PARAMS:
+ configDict.DirectoryService.params = copy.deepcopy(DEFAULT_SERVICE_PARAMS[dsType])
+ else:
+ configDict.DirectoryService.params = {}
+
+ for param in items.get("DirectoryService", {}).get("params", {}):
+ if dsType in DEFAULT_SERVICE_PARAMS and param not in DEFAULT_SERVICE_PARAMS[dsType]:
+ log.warn("Parameter %s is not supported by service %s" % (param, dsType))
+
+def _postUpdateDirectoryService(configDict):
+ if configDict.DirectoryService.type in DEFAULT_SERVICE_PARAMS:
+ for param in tuple(configDict.DirectoryService.params):
+ if param not in DEFAULT_SERVICE_PARAMS[configDict.DirectoryService.type]:
+ del configDict.DirectoryService.params[param]
+
+def _updateACLs(configDict):
+ #
+ # Base resource ACLs
+ #
+ def readOnlyACE(allowAnonymous):
+ if allowAnonymous:
+ reader = davxml.All()
+ else:
+ reader = davxml.Authenticated()
+
+ return davxml.ACE(
+ davxml.Principal(reader),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ ),
+ davxml.Protected(),
+ )
+
+ configDict.AdminACEs = tuple(
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(principal)),
+ davxml.Grant(davxml.Privilege(davxml.All())),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ )
+ for principal in configDict.AdminPrincipals
+ )
+
+ configDict.ReadACEs = tuple(
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(principal)),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ ),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ )
+ for principal in configDict.ReadPrincipals
+ )
+
+ configDict.RootResourceACL = davxml.ACL(
+ # Read-only for anon or authenticated, depending on config
+ readOnlyACE(configDict.EnableAnonymousReadRoot),
+
+ # Add inheritable all access for admins
+ *configDict.AdminACEs
+ )
+
+ log.debug("Root ACL: %s" % (configDict.RootResourceACL.toxml(),))
+
+ configDict.ProvisioningResourceACL = davxml.ACL(
+ # Read-only for anon or authenticated, depending on config
+ readOnlyACE(configDict.EnableAnonymousReadNav),
+
+ # Add read and read-acl access for admins
+ *[
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(principal)),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadACL()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ ),
+ davxml.Protected(),
+ )
+ for principal in configDict.AdminPrincipals
+ ]
+ )
+
+ log.debug("Nav ACL: %s" % (configDict.ProvisioningResourceACL.toxml(),))
+
+def _updateRejectClients(configDict):
+ #
+ # Compile RejectClients expressions for speed
+ #
+ try:
+ configDict.RejectClients = [re.compile(x) for x in configDict.RejectClients if x]
+ except re.error, e:
+ raise ConfigurationError("Invalid regular expression in RejectClients: %s" % (e,))
+
+def _updateDropBox(configDict):
+ #
+ # FIXME: Use the config object instead of doing this here
+ #
+ from twistedcaldav.resource import CalendarPrincipalResource
+ CalendarPrincipalResource.enableDropBox(configDict.EnableDropBox)
+
+def _updateLogLevels(configDict):
+ clearLogLevels()
+
+ try:
+ if "DefaultLogLevel" in configDict:
+ level = configDict["DefaultLogLevel"]
+ if not level:
+ level = "warn"
+ setLogLevelForNamespace(None, level)
+
+ if "LogLevels" in configDict:
+ for namespace in configDict["LogLevels"]:
+ setLogLevelForNamespace(namespace, configDict["LogLevels"][namespace])
+
+ except InvalidLogLevelError, e:
+ raise ConfigurationError("Invalid log level: %s" % (e.level))
+
+def _updateNotifications(configDict):
+ #
+ # Notifications
+ #
+
+ # Reloading not supported -- requires process running as root
+ if getattr(configDict, "_reloading", False):
+ return
+
+ for key, service in configDict.Notifications["Services"].iteritems():
+ if service["Enabled"]:
+ configDict.Notifications["Enabled"] = True
+ break
+ else:
+ configDict.Notifications["Enabled"] = False
+
+ for key, service in configDict.Notifications["Services"].iteritems():
+ if (
+ service["Service"] == "twistedcaldav.notify.XMPPNotifierService" and
+ service["Enabled"]
+ ):
+ # Get password from keychain. If not there, fall back to what
+ # is in the plist.
+ try:
+ password = getPasswordFromKeychain(service["JID"])
+ service["Password"] = password
+ log.info("XMPP password successfully retreived from keychain")
+ except KeychainAccessError:
+ # The system doesn't support keychain
+ pass
+ except KeychainPasswordNotFound:
+ # The password doesn't exist in the keychain.
+ log.error("XMPP password not found in keychain")
+
+ # Check for empty fields
+ for key, value in service.iteritems():
+ if not value and key not in ("AllowedJIDs", "HeartbeatMinutes", "Password"):
+ raise ConfigurationError("Invalid %s for XMPPNotifierService: %r"
+ % (key, value))
+
+def _updateScheduling(configDict):
+ #
+ # Scheduling
+ #
+
+ # Reloading not supported -- requires process running as root
+ if getattr(configDict, "_reloading", False):
+ return
+
+ service = configDict.Scheduling["iMIP"]
+
+ if service["Enabled"]:
+
+ # Get password for the user that is allowed to inject iMIP replies
+ # to the server's /inbox; if not available, fall back to plist
+ if service["Username"]:
+ try:
+ service["Password"] = getPasswordFromKeychain(service["Username"])
+ except KeychainAccessError:
+ # The system doesn't support keychain
+ pass
+ except KeychainPasswordNotFound:
+ # The password doesn't exist in the keychain.
+ log.info("iMIP injecting password not found in keychain")
+
+ for direction in ("Sending", "Receiving"):
+ if service[direction].Username:
+ # Get password from keychain. If not there, fall back to
+ # what is in the plist.
+ try:
+ account = "%s@%s" % (
+ service[direction].Username,
+ service[direction].Server
+ )
+ password = getPasswordFromKeychain(account)
+ service[direction]["Password"] = password
+ log.info("iMIP %s password successfully retreived from keychain" % (direction,))
+ except KeychainAccessError:
+ # The system doesn't support keychain
+ pass
+ except KeychainPasswordNotFound:
+ # The password doesn't exist in the keychain.
+ log.info("iMIP %s password not found in keychain" %
+ (direction,))
+
+PRE_UPDATE_HOOKS = (
+ _preUpdateDirectoryService,
+ )
+POST_UPDATE_HOOKS = (
+ _updateHostName,
+ _postUpdateDirectoryService,
+ _updateACLs,
+ _updateRejectClients,
+ _updateDropBox,
+ _updateLogLevels,
+ _updateNotifications,
+ _updateScheduling,
+ )
+
+def _cleanup(configDict, defaultDict):
+ cleanDict = copy.deepcopy(configDict)
+
+ def unknown(key):
+ config_key = "ICAL_SERVER_CONFIG_VALIDATION"
+ config_key_value = "loose"
+ if config_key in os.environ and os.environ[config_key] == config_key_value:
+ pass
+ else:
+ log.err("Ignoring unknown configuration option: %r - you can optionally bypass this validation by setting a sys env var %s to '%s'" % (key, config_key, config_key_value))
+ del cleanDict[key]
+
+ def deprecated(oldKey, newKey):
+ log.err("Configuration option %r is deprecated in favor of %r." % (oldKey, newKey))
+ if oldKey in configDict and newKey in configDict:
+ raise ConfigurationError(
+ "Both %r and %r options are specified; use the %r option only."
+ % (oldKey, newKey, newKey)
+ )
+
+ def renamed(oldKey, newKey):
+ deprecated(oldKey, newKey)
+ cleanDict[newKey] = configDict[oldKey]
+ del cleanDict[oldKey]
+
+ renamedOptions = {
+# "BindAddress": "BindAddresses",
+ }
+
+ for key in configDict:
+ if key in defaultDict:
+ continue
+
+ elif key in renamedOptions:
+ renamed(key, renamedOptions[key])
+
+# elif key == "pydirConfig":
+# deprecated(key, "PythonDirector -> pydir")
+# if "PythonDirector" not in cleanDict:
+# cleanDict["PythonDirector"] = {}
+# cleanDict["PythonDirector"]["ConfigFile"] = cleanDict["pydirConfig"]
+# del cleanDict["pydirConfig"]
+
+ else:
+ unknown(key,)
+
+ return cleanDict
+
+config.setProvider(PListConfigProvider(DEFAULT_CONFIG))
+config.addPreUpdateHook(PRE_UPDATE_HOOKS)
+config.addPostUpdateHook(POST_UPDATE_HOOKS)
Modified: CalendarServer/trunk/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_config.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/twistedcaldav/test/test_config.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -17,8 +17,9 @@
from twext.python.plistlib import writePlist
from twistedcaldav.log import logLevelForNamespace
-from twistedcaldav.config import config, defaultConfig
+from twistedcaldav.config import config, ConfigDict
from twistedcaldav.static import CalDAVFile
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, PListConfigProvider
from twistedcaldav.test.util import TestCase
testConfig = """<?xml version="1.0" encoding="UTF-8"?>
@@ -51,30 +52,29 @@
class ConfigTests(TestCase):
def setUp(self):
TestCase.setUp(self)
- config.update(defaultConfig)
+ config.setProvider(PListConfigProvider(DEFAULT_CONFIG))
self.testConfig = self.mktemp()
open(self.testConfig, "w").write(testConfig)
def tearDown(self):
- config.setDefaults(defaultConfig)
- config.loadConfig(None)
- config.reload()
+ config.setDefaults(DEFAULT_CONFIG)
+ config.reset()
def testDefaults(self):
- for key, value in defaultConfig.iteritems():
+ for key, value in DEFAULT_CONFIG.iteritems():
self.assertEquals(getattr(config, key), value)
def testLoadConfig(self):
self.assertEquals(config.ResponseCompression, True)
- config.loadConfig(self.testConfig)
+ config.load(self.testConfig)
self.assertEquals(config.ResponseCompression, False)
def testScoping(self):
self.assertEquals(config.ResponseCompression, True)
- config.loadConfig(self.testConfig)
+ config.load(self.testConfig)
self.assertEquals(config.ResponseCompression, False)
@@ -83,7 +83,7 @@
def testReloading(self):
self.assertEquals(config.HTTPPort, 0)
- config.loadConfig(self.testConfig)
+ config.load(self.testConfig)
self.assertEquals(config.HTTPPort, 8008)
@@ -96,7 +96,7 @@
def testUpdateAndReload(self):
self.assertEquals(config.HTTPPort, 0)
- config.loadConfig(self.testConfig)
+ config.load(self.testConfig)
self.assertEquals(config.HTTPPort, 8008)
@@ -191,7 +191,7 @@
def testUpdateDefaults(self):
self.assertEquals(config.SSLPort, 0)
- config.loadConfig(self.testConfig)
+ config.load(self.testConfig)
config.updateDefaults({"SSLPort": 8009})
@@ -206,12 +206,12 @@
def testMergeDefaults(self):
config.updateDefaults({"MultiProcess": {}})
- self.assertEquals(config._defaults["MultiProcess"]["LoadBalancer"]["Enabled"], True)
+ self.assertEquals(config._provider.getDefaults().MultiProcess.LoadBalancer.Enabled, True)
def testSetDefaults(self):
config.updateDefaults({"SSLPort": 8443})
- config.setDefaults(defaultConfig)
+ config.setDefaults(DEFAULT_CONFIG)
config.reload()
@@ -220,7 +220,7 @@
def testCopiesDefaults(self):
config.updateDefaults({"Foo": "bar"})
- self.assertNotIn("Foo", defaultConfig)
+ self.assertNotIn("Foo", DEFAULT_CONFIG)
def testComplianceClasses(self):
resource = CalDAVFile("/")
@@ -238,7 +238,7 @@
self.assertEquals(logLevelForNamespace(None), "warn")
self.assertEquals(logLevelForNamespace("some.namespace"), "warn")
- config.loadConfig(self.testConfig)
+ config.load(self.testConfig)
self.assertEquals(logLevelForNamespace(None), "info")
self.assertEquals(logLevelForNamespace("some.namespace"), "debug")
@@ -248,3 +248,52 @@
self.assertEquals(logLevelForNamespace(None), "warn")
self.assertEquals(logLevelForNamespace("some.namespace"), "warn")
+
+ def test_ConfigDict(self):
+ configDict = ConfigDict({
+ "a": "A",
+ "b": "B",
+ "c": "C",
+ })
+
+ # Test either syntax inbound
+ configDict["d"] = "D"
+ configDict.e = "E"
+
+ # Test either syntax outbound
+ for key in "abcde":
+ value = key.upper()
+
+ self.assertEquals(configDict[key], value)
+ self.assertEquals(configDict.get(key), value)
+ self.assertEquals(getattr(configDict, key), value)
+
+ self.assertIn(key, configDict)
+ self.assertTrue(hasattr(configDict, key))
+
+ self.assertEquals(configDict.a, "A")
+ self.assertEquals(configDict.d, "D")
+ self.assertEquals(configDict.e, "E")
+
+ # Test either syntax for delete
+ del configDict["d"]
+ delattr(configDict, "e")
+
+ # Test either syntax for absence
+ for key in "de":
+ self.assertNotIn(key, configDict)
+ self.assertFalse(hasattr(configDict, key))
+ self.assertRaises(KeyError, lambda: configDict[key])
+ self.assertRaises(AttributeError, getattr, configDict, key)
+
+ self.assertRaises(AttributeError, lambda: configDict.e)
+ self.assertRaises(AttributeError, lambda: configDict.f)
+
+ # Keys may not begin with "_" in dict syntax
+ def set():
+ configDict["_x"] = "X"
+ self.assertRaises(KeyError, set)
+
+ # But attr syntax is OK
+ configDict._x = "X"
+ self.assertEquals(configDict._x, "X")
Modified: CalendarServer/trunk/twistedcaldav/test/test_notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_notify.py 2009-07-09 20:33:18 UTC (rev 4443)
+++ CalendarServer/trunk/twistedcaldav/test/test_notify.py 2009-07-09 21:53:36 UTC (rev 4444)
@@ -18,8 +18,8 @@
from twisted.words.protocols.jabber.client import IQ
from twisted.words.protocols.jabber.error import StanzaError
from twistedcaldav.notify import *
-from twistedcaldav import config as config_mod
from twistedcaldav.config import Config
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, PListConfigProvider
from twistedcaldav.test.util import TestCase
@@ -42,7 +42,7 @@
notificationClient = getNotificationClient()
self.assertNotEquals(notificationClient, None)
- enabledConfig = Config(config_mod.defaultConfig)
+ enabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
enabledConfig.Notifications['Enabled'] = True
clientNotifier = ClientNotifier(StubResource("a"),
configOverride=enabledConfig)
@@ -342,12 +342,12 @@
class XMPPNotifierTests(TestCase):
- xmppEnabledConfig = Config(config_mod.defaultConfig)
+ xmppEnabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
xmppEnabledConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = True
xmppEnabledConfig.ServerHostName = "server.example.com"
xmppEnabledConfig.HTTPPort = 80
- xmppDisabledConfig = Config(config_mod.defaultConfig)
+ xmppDisabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
xmppDisabledConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = False
def setUp(self):
@@ -478,7 +478,7 @@
def test_sendHeartbeat(self):
- xmppConfig = Config(config_mod.defaultConfig)
+ xmppConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
xmppConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = True
xmppConfig.ServerHostName = "server.example.com"
xmppConfig.HTTPPort = 80
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090709/af76a7ff/attachment-0001.html>
More information about the calendarserver-changes
mailing list