[CalendarServer-changes] [4958] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Mon Jan 25 07:56:35 PST 2010
Revision: 4958
http://trac.macosforge.org/projects/calendarserver/changeset/4958
Author: cdaboo at apple.com
Date: 2010-01-25 07:56:33 -0800 (Mon, 25 Jan 2010)
Log Message:
-----------
Merged partition branch to trunk.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/provision/root.py
CalendarServer/trunk/calendarserver/sidecar/task.py
CalendarServer/trunk/calendarserver/tap/caldav.py
CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
CalendarServer/trunk/calendarserver/tools/principals.py
CalendarServer/trunk/calendarserver/tools/util.py
CalendarServer/trunk/calendarserver/webadmin/resource.py
CalendarServer/trunk/conf/auth/accounts-test.xml
CalendarServer/trunk/conf/auth/accounts.dtd
CalendarServer/trunk/conf/auth/accounts.xml
CalendarServer/trunk/conf/caldavd-apple.plist
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/conf/caldavd.plist
CalendarServer/trunk/contrib/migration/59_calendarmigrator.py
CalendarServer/trunk/memcacheclient.py
CalendarServer/trunk/support/build.sh
CalendarServer/trunk/twistedcaldav/cache.py
CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
CalendarServer/trunk/twistedcaldav/directory/cachingdirectory.py
CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/trunk/twistedcaldav/directory/digest.py
CalendarServer/trunk/twistedcaldav/directory/directory.py
CalendarServer/trunk/twistedcaldav/directory/idirectory.py
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/directory/sudo.py
CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
CalendarServer/trunk/twistedcaldav/directory/test/test_aggregate.py
CalendarServer/trunk/twistedcaldav/directory/test/test_cachedirectory.py
CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py
CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py
CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py
CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py
CalendarServer/trunk/twistedcaldav/directory/xmlfile.py
CalendarServer/trunk/twistedcaldav/extensions.py
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/index.py
CalendarServer/trunk/twistedcaldav/log.py
CalendarServer/trunk/twistedcaldav/mail.py
CalendarServer/trunk/twistedcaldav/memcachepool.py
CalendarServer/trunk/twistedcaldav/memcacheprops.py
CalendarServer/trunk/twistedcaldav/memcacher.py
CalendarServer/trunk/twistedcaldav/method/report_common.py
CalendarServer/trunk/twistedcaldav/notify.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/scheduling/addressmapping.py
CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
CalendarServer/trunk/twistedcaldav/scheduling/cuaddress.py
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py
CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py
CalendarServer/trunk/twistedcaldav/scheduling/processing.py
CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py
CalendarServer/trunk/twistedcaldav/scheduling/utils.py
CalendarServer/trunk/twistedcaldav/static.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/twistedcaldav/test/test_cache.py
CalendarServer/trunk/twistedcaldav/test/test_config.py
CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
CalendarServer/trunk/twistedcaldav/test/util.py
CalendarServer/trunk/twistedcaldav/upgrade.py
Added Paths:
-----------
CalendarServer/trunk/bin/calendarserver_load_augmentdb
CalendarServer/trunk/bin/calendarserver_manage_augments
CalendarServer/trunk/calendarserver/tools/loadaugmentdb.py
CalendarServer/trunk/calendarserver/tools/manageaugments.py
CalendarServer/trunk/calendarserver/tools/managepostgres.py
CalendarServer/trunk/conf/auth/augments-test.xml
CalendarServer/trunk/conf/auth/augments.dtd
CalendarServer/trunk/conf/auth/proxies-test.xml
CalendarServer/trunk/conf/auth/proxies.dtd
CalendarServer/trunk/conf/partitions-test.plist
CalendarServer/trunk/conf/partitions.plist
CalendarServer/trunk/twistedcaldav/client/
CalendarServer/trunk/twistedcaldav/client/__init__.py
CalendarServer/trunk/twistedcaldav/client/pool.py
CalendarServer/trunk/twistedcaldav/client/reverseproxy.py
CalendarServer/trunk/twistedcaldav/database.py
CalendarServer/trunk/twistedcaldav/directory/augment.py
CalendarServer/trunk/twistedcaldav/directory/calendaruserproxyloader.py
CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml
CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml
CalendarServer/trunk/twistedcaldav/directory/test/augments.xml
CalendarServer/trunk/twistedcaldav/directory/test/proxies.xml
CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py
CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py
CalendarServer/trunk/twistedcaldav/partitions.py
CalendarServer/trunk/twistedcaldav/test/test_database.py
Removed Paths:
-------------
CalendarServer/trunk/conf/auth/accounts.htauth
CalendarServer/trunk/conf/auth/accounts.htdigest
CalendarServer/trunk/conf/auth/groups
CalendarServer/trunk/twistedcaldav/client/__init__.py
CalendarServer/trunk/twistedcaldav/client/pool.py
CalendarServer/trunk/twistedcaldav/client/reverseproxy.py
CalendarServer/trunk/twistedcaldav/directory/apache.py
CalendarServer/trunk/twistedcaldav/directory/sqldb.py
CalendarServer/trunk/twistedcaldav/directory/test/basic
CalendarServer/trunk/twistedcaldav/directory/test/digest
CalendarServer/trunk/twistedcaldav/directory/test/groups
CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectoryschema.py
CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
Property Changed:
----------------
CalendarServer/trunk/
CalendarServer/trunk/doc/Extensions/caldav-privatecomments.txt
CalendarServer/trunk/doc/Extensions/caldav-privatecomments.xml
Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/more-deferreds:4446,4462
/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/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
Deleted: svk:merge
- e27351fd-9f3e-4f54-a53b-843176b1656c:/CalendarServer/branches/more-deferreds:4462
Copied: CalendarServer/trunk/bin/calendarserver_load_augmentdb (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/bin/calendarserver_load_augmentdb)
===================================================================
--- CalendarServer/trunk/bin/calendarserver_load_augmentdb (rev 0)
+++ CalendarServer/trunk/bin/calendarserver_load_augmentdb 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+ if "PYTHONPATH" in globals():
+ sys.path.insert(0, PYTHONPATH)
+ else:
+ from os.path import dirname, abspath, join
+ from subprocess import Popen, PIPE
+
+ home = dirname(dirname(abspath(__file__)))
+ run = join(home, "run")
+
+ child = Popen((run, "-p"), stdout=PIPE)
+ path, stderr = child.communicate()
+
+ if child.wait() == 0:
+ sys.path[0:0] = path.split(":")
+
+ from calendarserver.tools.loadaugmentdb import main
+ main()
Copied: CalendarServer/trunk/bin/calendarserver_manage_augments (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/bin/calendarserver_manage_augments)
===================================================================
--- CalendarServer/trunk/bin/calendarserver_manage_augments (rev 0)
+++ CalendarServer/trunk/bin/calendarserver_manage_augments 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+ if "PYTHONPATH" in globals():
+ sys.path.insert(0, PYTHONPATH)
+ else:
+ from os.path import dirname, abspath, join
+ from subprocess import Popen, PIPE
+
+ home = dirname(dirname(abspath(__file__)))
+ run = join(home, "run")
+
+ child = Popen((run, "-p"), stdout=PIPE)
+ path, stderr = child.communicate()
+
+ if child.wait() == 0:
+ sys.path[0:0] = path.split(":")
+
+ from calendarserver.tools.manageaugments import main
+ main()
Modified: CalendarServer/trunk/calendarserver/provision/root.py
===================================================================
--- CalendarServer/trunk/calendarserver/provision/root.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/calendarserver/provision/root.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@
self.contentFilters = []
- if config.Memcached["ClientEnabled"]:
+ if config.Memcached.Pools.Default.ClientEnabled:
self.responseCache = MemcacheResponseCache(self.fp)
CalendarHomeFile.cacheNotifierFactory = MemcacheChangeNotifier
@@ -244,6 +244,15 @@
"Your client software (%s) is not allowed to access this service." % (agent,)
))
+ # Look for forwarding
+ if config.Partitioning.Enabled:
+ remote_ip = request.headers.getRawHeaders('x-forwarded-for')
+ if remote_ip and len(remote_ip) == 1:
+ request.forwarded_for = remote_ip[0]
+ if not hasattr(request, "extendedLogItems"):
+ request.extendedLogItems = {}
+ request.extendedLogItems["xff"] = remote_ip[0]
+
if request.method == "PROPFIND" and not getattr(request, "notInCache", False) and len(segments) > 1:
try:
authnUser, authzUser = (yield self.authenticate(request))
Modified: CalendarServer/trunk/calendarserver/sidecar/task.py
===================================================================
--- CalendarServer/trunk/calendarserver/sidecar/task.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/calendarserver/sidecar/task.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,4 +1,4 @@
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -379,15 +379,10 @@
#
# Configure Memcached Client Pool
#
- if config.Memcached.ClientEnabled:
- memcachepool.installPool(
- IPv4Address(
- "TCP",
- config.Memcached.BindAddress,
- config.Memcached.Port,
- ),
- config.Memcached.MaxClients,
- )
+ memcachepool.installPools(
+ config.Memcached.Pools,
+ config.Memcached.MaxClients,
+ )
#
# Configure NotificationClient
Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,6 +1,6 @@
# -*- test-case-name: calendarserver.tap.test.test_caldav -*-
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@
from twisted.python.usage import Options, UsageError
from twisted.python.reflect import namedClass
from twisted.plugin import IPlugin
+from twisted.internet import reactor
from twisted.internet.reactor import callLater, spawnProcess
from twisted.internet.process import ProcessExitedAlready
from twisted.internet.protocol import Protocol, Factory
@@ -64,32 +65,35 @@
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "support"))
from version import version as getVersion
version = "%s (%s)" % getVersion()
-from twistedcaldav.log import Logger, LoggingMixIn
-from twistedcaldav.log import logLevelForNamespace, setLogLevelForNamespace
+from twistedcaldav import memcachepool
+from twistedcaldav.accesslog import AMPCommonAccessLoggingObserver
+from twistedcaldav.accesslog import AMPLoggingFactory
from twistedcaldav.accesslog import DirectoryLogWrapperResource
from twistedcaldav.accesslog import RotatingFileAccessLoggingObserver
-from twistedcaldav.accesslog import AMPLoggingFactory
-from twistedcaldav.accesslog import AMPCommonAccessLoggingObserver
-from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
+from twistedcaldav.config import ConfigurationError
from twistedcaldav.config import config
-from twistedcaldav.config import ConfigurationError
-from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
+from twistedcaldav.directory import augment, calendaruserproxy
+from twistedcaldav.directory.aggregate import AggregateDirectoryService
+from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
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.util import NotFilePath
from twistedcaldav.directory.wiki import WikiDirectoryService
+from twistedcaldav.localization import processLocalizationFiles
+from twistedcaldav.log import Logger, LoggingMixIn
+from twistedcaldav.log import logLevelForNamespace, setLogLevelForNamespace
+from twistedcaldav.mail import IMIPReplyInboxResource
+from twistedcaldav.notify import installNotificationClient
+from twistedcaldav.pdmonster import PDClientAddressWrapper
+from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
from twistedcaldav.static import CalendarHomeProvisioningFile
from twistedcaldav.static import IScheduleInboxFile
from twistedcaldav.static import TimezoneServiceFile
-from twistedcaldav.mail import IMIPReplyInboxResource
+from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
from twistedcaldav.timezones import TimezoneCache
from twistedcaldav.upgrade import upgradeData
-from twistedcaldav import memcachepool
-from twistedcaldav.notify import installNotificationClient
from twistedcaldav.util import getNCPU
-from twistedcaldav.localization import processLocalizationFiles
try:
from twistedcaldav.authkerb import NegotiateCredentialFactory
@@ -409,10 +413,10 @@
# Now do any on disk upgrades we might need.
# Memcache isn't running at this point, so temporarily change
# the config so nobody tries to talk to it while upgrading
- memcacheSetting = config.Memcached.ClientEnabled
- config.Memcached.ClientEnabled = False
+ memcacheSetting = config.Memcached.Pools.Default.ClientEnabled
+ config.Memcached.Pools.Default.ClientEnabled = False
upgradeData(config)
- config.Memcached.ClientEnabled = memcacheSetting
+ config.Memcached.Pools.Default.ClientEnabled = memcacheSetting
service = serviceMethod(options)
@@ -517,17 +521,48 @@
SudoDirectoryService.recordType_sudoers)
#
+ # Setup the Augment Service
+ #
+ augmentClass = namedClass(config.AugmentService.type)
+
+ self.log_info("Configuring augment service of type: %s" % (augmentClass,))
+
+ try:
+ augment.AugmentService = augmentClass(**config.AugmentService.params)
+ except IOError, e:
+ self.log_error("Could not start augment service")
+ raise
+
+ #
+ # Setup the PoxyDB Service
+ #
+ proxydbClass = namedClass(config.ProxyDBService.type)
+
+ self.log_info("Configuring proxydb service of type: %s" % (proxydbClass,))
+
+ try:
+ calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
+ except IOError, e:
+ self.log_error("Could not start proxydb service")
+ raise
+
+ #
+ # Make sure proxies get initialized
+ #
+ if config.ProxyLoadFromFile:
+ def _doProxyUpdate():
+ loader = XMLCalendarUserProxyLoader(config.ProxyLoadFromFile)
+ return loader.updateProxyDB()
+
+ reactor.addSystemEventTrigger("after", "startup", _doProxyUpdate)
+
+ #
# Configure Memcached Client Pool
#
- if config.Memcached.ClientEnabled:
- memcachepool.installPool(
- IPv4Address(
- "TCP",
- config.Memcached.BindAddress,
- config.Memcached.Port,
- ),
- config.Memcached.MaxClients,
- )
+ memcachepool.installPools(
+ config.Memcached.Pools,
+ config.Memcached.MaxClients,
+ )
#
# Configure NotificationClient
@@ -1037,26 +1072,24 @@
- if config.Memcached.ServerEnabled:
- self.log_info("Adding memcached service")
+ for name, pool in config.Memcached.Pools.items():
+ if pool.ServerEnabled:
+ self.log_info("Adding memcached service for pool: %s" % (name,))
+
+ memcachedArgv = [
+ config.Memcached.memcached,
+ "-p", str(pool.Port),
+ "-l", pool.BindAddress,
+ "-U", "0",
+ ]
+
+ if config.Memcached.MaxMemory is not 0:
+ memcachedArgv.extend(["-m", str(config.Memcached.MaxMemory)])
+
+ memcachedArgv.extend(config.Memcached.Options)
+
+ monitor.addProcess('memcached-%s' % (name,), memcachedArgv, env=parentEnv)
- memcachedArgv = [
- config.Memcached.memcached,
- "-p", str(config.Memcached.Port),
- "-l", config.Memcached.BindAddress,
- "-U", "0",
- ]
-
- if config.Memcached.MaxMemory is not 0:
- memcachedArgv.extend(["-m", str(config.Memcached.MaxMemory)])
-
- if config.UserName:
- memcachedArgv.extend(["-u", config.UserName])
-
- memcachedArgv.extend(config.Memcached.Options)
-
- monitor.addProcess("memcached", memcachedArgv, env=parentEnv)
-
if (
config.Notifications.Enabled and
config.Notifications.InternalNotificationHost == "localhost"
@@ -1212,9 +1245,6 @@
% (config.ControlPort,),
])
- if config.Memcached.ServerEnabled:
- args.extend(["-o", "Memcached/ClientEnabled=True"])
-
if self.inheritFDs:
args.extend([
"-o", "InheritFDs=%s" % (",".join(map(str, self.inheritFDs)),)
Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2007 Apple Inc. All rights reserved.
+# Copyright (c) 2007-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -185,6 +185,7 @@
self.config = ConfigDict(DEFAULT_CONFIG)
accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml")
+ augmentsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/augments.xml")
pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
self.config["DirectoryService"] = {
@@ -192,6 +193,11 @@
"type": "twistedcaldav.directory.xmlfile.XMLDirectoryService"
}
+ self.config["AugmentService"] = {
+ "params": {"xmlFiles": [augmentsFile]},
+ "type": "twistedcaldav.directory.augment.AugmentXMLDB"
+ }
+
self.config.DocumentRoot = self.mktemp()
self.config.DataRoot = self.mktemp()
self.config.ProcessType = "Slave"
Copied: CalendarServer/trunk/calendarserver/tools/loadaugmentdb.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/calendarserver/tools/loadaugmentdb.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/loadaugmentdb.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/loadaugmentdb.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,185 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from calendarserver.tools.util import loadConfig, getDirectory,\
+ autoDisableMemcached
+from getopt import getopt, GetoptError
+from grp import getgrnam
+from pwd import getpwnam
+from sys import stdout, stderr
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.log import addObserver, removeObserver
+from twisted.python.util import switchUID
+from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.directory import augment
+from twistedcaldav.directory.augment import AugmentXMLDB
+from twistedcaldav.log import setLogLevelForNamespace
+import os
+import sys
+
+class UsageError (StandardError):
+ pass
+
+class StandardIOObserver (object):
+ """
+ Log observer that writes to standard I/O.
+ """
+ def emit(self, eventDict):
+ text = None
+
+ if eventDict["isError"]:
+ output = stderr
+ if "failure" in eventDict:
+ text = eventDict["failure"].getTraceback()
+ else:
+ output = stdout
+
+ if not text:
+ text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
+
+ output.write(text)
+ output.flush()
+
+ def start(self):
+ addObserver(self.emit)
+
+ def stop(self):
+ removeObserver(self.emit)
+
+def usage(e=None):
+ if e:
+ print e
+ print ""
+
+ name = os.path.basename(sys.argv[0])
+ print "usage: %s [options]" % (name,)
+ print ""
+ print "Populate an sqlite or PostgreSQL augments database with values"
+ print "from an XML augments file."
+ print ""
+ print "options:"
+ print " -h --help: print this help and exit"
+ print " -f --config: Specify caldavd.plist configuration path"
+ print " -x --xmlfile: Specify xml augments file path"
+ print " -r --remove: Remove all entries from the database"
+
+ if e:
+ sys.exit(64)
+ else:
+ sys.exit(0)
+
+def main():
+ try:
+ (optargs, args) = getopt(
+ sys.argv[1:], "hf:rx:", [
+ "config=",
+ "remove",
+ "xmlfile=",
+ "help",
+ ],
+ )
+ except GetoptError, e:
+ usage(e)
+
+ configFileName = None
+ xmlFileName = None
+ remove = False
+
+ for opt, arg in optargs:
+ if opt in ("-h", "--help"):
+ usage()
+
+ elif opt in ("-f", "--config"):
+ configFileName = arg
+
+ elif opt in ("-r", "--remove"):
+ remove = True
+ if raw_input("Do you really want to remove all records from the database? [y/n] ") != "y":
+ sys.exit(0)
+
+ elif opt in ("-x", "--xmlfile"):
+ xmlFileName = arg
+
+ if args:
+ usage("Too many arguments: %s" % (" ".join(args),))
+
+ observer = StandardIOObserver()
+ observer.start()
+
+ #
+ # Get configuration
+ #
+ try:
+ loadConfig(configFileName)
+ setLogLevelForNamespace(None, "warn")
+
+ # Shed privileges
+ if config.UserName and config.GroupName and os.getuid() == 0:
+ uid = getpwnam(config.UserName).pw_uid
+ gid = getgrnam(config.GroupName).gr_gid
+ switchUID(uid, uid, gid)
+
+ os.umask(config.umask)
+
+ config.directory = getDirectory()
+ autoDisableMemcached(config)
+ except ConfigurationError, e:
+ usage("Unable to start: %s" % (e,))
+
+ try:
+ dbxml = AugmentXMLDB((xmlFileName,)) if not remove else None
+ except IOError, e:
+ usage("Could not read XML augment file: %s" % (e,))
+
+ #
+ # Start the reactor
+ #
+ reactor.callLater(0, run, dbxml)
+ reactor.run()
+
+ at inlineCallbacks
+def run(dbxml):
+
+ try:
+ guids = set((yield augment.AugmentService.getAllGUIDs()))
+ added = 0
+ updated = 0
+ removed = 0
+ if dbxml:
+ for record in dbxml.db.values():
+ yield augment.AugmentService.addAugmentRecord(record, record.guid in guids)
+ if record.guid in guids:
+ updated += 1
+ else:
+ added += 1
+ for guid in guids.difference(dbxml.db.keys()):
+ yield augment.AugmentService.removeAugmentRecord(guid)
+ removed += 1
+ else:
+ yield augment.AugmentService.clean()
+ removed = len(guids)
+
+ print "Changes:"
+ print " Added: %d" % (added,)
+ print " Changed: %d" % (updated,)
+ print " Removed: %d" % (removed,)
+ finally:
+ #
+ # Stop the reactor
+ #
+ reactor.stop()
Copied: CalendarServer/trunk/calendarserver/tools/manageaugments.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/calendarserver/tools/manageaugments.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/manageaugments.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/manageaugments.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,200 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from optparse import OptionParser
+from twistedcaldav.directory import xmlaugmentsparser
+from xml.etree.ElementTree import ElementTree, tostring, SubElement
+from xml.parsers.expat import ExpatError
+import sys
+
+def error(s):
+ print s
+ sys.exit(1)
+
+def readXML(xmlfile):
+
+ # Read in XML
+ try:
+ tree = ElementTree(file=xmlfile)
+ except ExpatError, e:
+ error("Unable to parse file '%s' because: %s" % (xmlfile, e,))
+
+ # Verify that top-level element is correct
+ augments_node = tree.getroot()
+ if augments_node.tag != xmlaugmentsparser.ELEMENT_AUGMENTS:
+ error("Ignoring file '%s' because it is not a augments file" % (xmlfile,))
+
+ return augments_node
+
+def writeXML(xmlfile, root):
+
+ data = """<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE augments SYSTEM "augments.dtd">
+
+""" + tostring(root)
+
+ with open(xmlfile, "w") as f:
+ f.write(data)
+
+def addSubElement(parent, tag, text=None, indent=0):
+
+ child = SubElement(parent, tag)
+ child.text = text
+ child.tail = "\n" + " " * indent
+ return child
+
+def changeSubElementText(parent, tag, text):
+
+ child = parent.find(tag)
+ child.text = text
+
+def doAdd(xmlfile, guid, host, enable_calendar, auto_schedule):
+
+ augments_node = readXML(xmlfile)
+
+ # Make sure GUID is not already present
+ for child in augments_node.getchildren():
+
+ if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
+ error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, xmlfile,))
+
+ for node in child.getchildren():
+
+ if node.tag == xmlaugmentsparser.ELEMENT_GUID and node.text == guid:
+ error("Cannot add guid '%s' because it already exists in augment file: '%s'" % (guid, xmlfile,))
+
+ # Create new record
+ augments_node.getchildren()[-1].tail = "\n "
+ record = addSubElement(augments_node, xmlaugmentsparser.ELEMENT_RECORD, "\n ")
+ addSubElement(record, xmlaugmentsparser.ELEMENT_GUID, guid, 4)
+ addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLE, "true", 4)
+ addSubElement(record, xmlaugmentsparser.ELEMENT_HOSTEDAT, host, 4)
+ addSubElement(record, xmlaugmentsparser.ELEMENT_ENABLECALENDAR, "true" if enable_calendar else "false", 4)
+ addSubElement(record, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE, "true" if auto_schedule else "false", 2)
+
+ # Modify xmlfile
+ writeXML(xmlfile, augments_node)
+ print "Added guid '%s' in augment file: '%s'" % (guid, xmlfile,)
+
+def doModify(xmlfile, guid, host, enable_calendar, auto_schedule):
+
+ augments_node = readXML(xmlfile)
+
+ # Make sure GUID is present
+ for child in augments_node.getchildren():
+
+ if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
+ error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, xmlfile,))
+
+ for node in child.getchildren():
+
+ if node.tag == xmlaugmentsparser.ELEMENT_GUID and node.text == guid:
+ break
+ else:
+ continue
+ break
+ else:
+ error("Cannot modify guid '%s' because it does not exist in augment file: '%s'" % (guid, xmlfile,))
+
+ # Modify record
+ if host is not None:
+ child.find(xmlaugmentsparser.ELEMENT_HOSTEDAT).text = host
+ child.find(xmlaugmentsparser.ELEMENT_ENABLECALENDAR).text = "true" if enable_calendar else "false"
+ child.find(xmlaugmentsparser.ELEMENT_AUTOSCHEDULE).text = "true" if auto_schedule else "false"
+
+ # Modify xmlfile
+ writeXML(xmlfile, augments_node)
+ print "Modified guid '%s' in augment file: '%s'" % (guid, xmlfile,)
+
+def doRemove(xmlfile, guid):
+
+ augments_node = readXML(xmlfile)
+
+ # Make sure GUID is present
+ for child in augments_node.getchildren():
+
+ if child.tag != xmlaugmentsparser.ELEMENT_RECORD:
+ error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, xmlfile,))
+
+ for node in child.getchildren():
+
+ if node.tag == xmlaugmentsparser.ELEMENT_GUID and node.text == guid:
+ break
+ else:
+ continue
+ augments_node.remove(child)
+ break
+ else:
+ error("Cannot remove guid '%s' because it does not exist in augment file: '%s'" % (guid, xmlfile,))
+
+ # Modify xmlfile
+ writeXML(xmlfile, augments_node)
+ print "Removed guid '%s' from augment file: '%s'" % (guid, xmlfile,)
+
+def doPrint(xmlfile):
+
+ # Read in XML
+ augments_node = readXML(xmlfile)
+
+ print tostring(augments_node)
+
+def main():
+
+ usage = "%prog [options] ACTION"
+ epilog = """
+ACTION is one of add|modify|remove|print
+
+ add: add a user record
+ modify: modify a user record
+ remove: remove a user record
+ print: print all user records
+"""
+ description = "Tool to manipulate CalendarServer augments XML file"
+ version = "%prog v1.0"
+ parser = OptionParser(usage=usage, description=description, version=version)
+ parser.epilog = epilog
+ parser.format_epilog = lambda _:epilog
+
+ parser.add_option("-f", "--file", dest="xmlfilename",
+ help="XML augment file to manipulate", metavar="FILE")
+ parser.add_option("-g", "--guid", dest="guid",
+ help="OD GUID to manipulate", metavar="GUID")
+ parser.add_option("-n", "--node", dest="node",
+ help="Partition node to assign to GUID", metavar="NODE")
+ parser.add_option("-c", "--enable-calendar", action="store_true", dest="enable_calendar",
+ default=True, help="Enable calendaring for this GUID: %default")
+ parser.add_option("-a", "--auto-schedule", action="store_true", dest="auto_schedule",
+ default=False, help="Enable auto-schedule for this GUID: %default")
+
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ parser.error("incorrect number of arguments")
+
+ if args[0] == "add":
+ if not options.node:
+ parser.error("Partition node must be specified when adding")
+ doAdd(options.xmlfilename, options.guid, options.node, options.enable_calendar, options.auto_schedule)
+ elif args[0] == "modify":
+ doModify(options.xmlfilename, options.guid, options.node, options.enable_calendar, options.auto_schedule)
+ elif args[0] == "remove":
+ doRemove(options.xmlfilename, options.guid)
+ elif args[0] == "print":
+ doPrint(options.xmlfilename)
+
+if __name__ == '__main__':
+ main()
Copied: CalendarServer/trunk/calendarserver/tools/managepostgres.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/calendarserver/tools/managepostgres.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/managepostgres.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/managepostgres.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from optparse import OptionParser
+import os
+import subprocess
+import sys
+import time
+
+def error(s):
+ print s
+ sys.exit(1)
+
+def cmd(s):
+ print s
+ subprocess.call(s, shell=True)
+
+def doInit(basedir):
+
+ cmd("mkdir %s/data" % (basedir,))
+ cmd("%s/bin/initdb -D %s/data" % (basedir, basedir,))
+
+ # Have the DB listen on all interfaces
+ with open("%s/data/postgresql.conf" % (basedir,)) as f:
+ conf = f.read()
+ conf = conf.replace("#listen_addresses = 'localhost'", "listen_addresses = '*'\t")
+ conf = conf.replace("max_connections = 20 ", "max_connections = 500")
+ with open("%s/data/postgresql.conf" % (basedir,), "w") as f:
+ f.write(conf)
+
+ # Allow current user to auth to the DBs
+ with open("%s/data/pg_hba.conf" % (basedir,)) as f:
+ conf = f.read()
+ conf = conf.replace("127.0.0.1/32", "0.0.0.0/0 ")
+ with open("%s/data/pg_hba.conf" % (basedir,), "w") as f:
+ f.write(conf)
+
+ cmd("%s/bin/pg_ctl -D %s/data -l logfile start" % (basedir, basedir,))
+ time.sleep(5)
+ cmd("%s/bin/createdb proxies" % (basedir,))
+ cmd("%s/bin/createdb augments" % (basedir,))
+ cmd("%s/bin/pg_ctl -D %s/data -l logfile stop" % (basedir, basedir,))
+
+def doStart(basedir):
+
+ cmd("%s/bin/pg_ctl -D %s/data -l logfile start" % (basedir, basedir,))
+
+def doStop(basedir):
+
+ cmd("%s/bin/pg_ctl -D %s/data -l logfile stop" % (basedir, basedir,))
+
+def doRun(basedir, verbose):
+
+ cmd("%s/bin/postgres %s -D %s/data" % (basedir, "-d 3" if verbose else "", basedir,))
+
+def doClean(basedir):
+
+ cmd("rm -rf %s/data" % (basedir, basedir,))
+
+if __name__ == '__main__':
+
+ usage = "%prog [options] ACTION"
+ epilog = """
+ACTION is one of init|start|stop|run
+
+ init: initialize databases
+ start: start postgres daemon
+ stop: stop postgres daemon
+ run: run postgres (non-daemon)
+ clean: remove databases
+
+"""
+ description = "Tool to manage PostgreSQL"
+ version = "%prog v1.0"
+ parser = OptionParser(usage=usage, description=description, version=version)
+ parser.epilog = epilog
+ parser.format_epilog = lambda _:epilog
+
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
+ default=True, help="Use debug logging for PostgreSQL")
+ parser.add_option("-d", "--base-dir", dest="basedir",
+ default="%s/../postgresql-8.4.1/_root" % (os.getcwd(),), help="Base directory for PostgreSQL install")
+
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ parser.error("incorrect number of arguments")
+
+ if args[0] == "init":
+ doInit(options.basedir)
+ elif args[0] == "start":
+ doStart(options.basedir)
+ elif args[0] == "stop":
+ doStop(options.basedir)
+ elif args[0] == "run":
+ doRun(options.basedir, options.verbose)
+ elif args[0] == "clean":
+ doClean(options.basedir)
+ else:
+ parser.error("incorrect argument '%s'" % (args[0],))
Modified: CalendarServer/trunk/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/principals.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/calendarserver/tools/principals.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,7 +1,7 @@
#!/usr/bin/env python
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -265,15 +265,10 @@
#
# Connect to memcached, notifications
#
- if config.Memcached.ClientEnabled:
- memcachepool.installPool(
- IPv4Address(
- "TCP",
- config.Memcached.BindAddress,
- config.Memcached.Port,
- ),
- config.Memcached.MaxClients
- )
+ memcachepool.installPools(
+ config.Memcached.Pools,
+ config.Memcached.MaxClients
+ )
if config.Notifications.Enabled:
installNotificationClient(
config.Notifications.InternalNotificationHost,
@@ -479,11 +474,11 @@
{ True: "true", False: "false" }[autoSchedule],
principal,
)
- (yield principal.setAutoSchedule(autoSchedule))
+ principal.setAutoSchedule(autoSchedule)
@inlineCallbacks
def action_getAutoSchedule(principal):
- autoSchedule = (yield principal.getAutoSchedule())
+ autoSchedule = principal.getAutoSchedule()
print "Autoschedule for %s is %s" % (
principal,
{ True: "true", False: "false" }[autoSchedule],
Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/calendarserver/tools/util.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
import socket
from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.directory import augment, calendaruserproxy
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
@@ -88,6 +89,13 @@
return self.principalCollection.principalForCalendarUserAddress(cua)
+ # Load augment/proxy db classes now
+ augmentClass = namedClass(config.AugmentService.type)
+ augment.AugmentService = augmentClass(**config.AugmentService.params)
+
+ proxydbClass = namedClass(config.ProxyDBService.type)
+ calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
+
# Wait for directory service to become available
directory = MyDirectoryService(config.DirectoryService.params)
while not directory.isAvailable():
@@ -128,14 +136,14 @@
If memcached is not running, set config.Memcached.ClientEnabled to False
"""
- if not config.Memcached.ClientEnabled:
+ if not config.Memcached.Pools.Default.ClientEnabled:
return
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
- s.connect((config.Memcached.BindAddress, config.Memcached.Port))
+ s.connect((config.Memcached.Pools.Default.BindAddress, config.Memcached.Pools.Default.Port))
s.close()
except socket.error:
- config.Memcached.ClientEnabled = False
+ config.Memcached.Pools.Default.ClientEnabled = False
Modified: CalendarServer/trunk/calendarserver/webadmin/resource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/resource.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/calendarserver/webadmin/resource.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -154,7 +154,7 @@
# Update the auto-schedule value if specified.
if autoSchedule is not None and (autoSchedule == "true" or autoSchedule == "false"):
if principal.record.recordType != "users" and principal.record.recordType != "groups":
- (yield principal.setAutoSchedule(autoSchedule == "true"))
+ principal.setAutoSchedule(autoSchedule == "true")
# Update the proxies if specified.
for proxyId in removeProxies:
@@ -277,7 +277,7 @@
###
autoScheduleHtml = ""
if resource.record.recordType != "users" and resource.record.recordType != "groups":
- autoSchedule = (yield resource.getAutoSchedule())
+ autoSchedule = resource.getAutoSchedule()
autoScheduleHtml = """
<div style=\"margin-top:15px; border-bottom:1px #444444 dotted\"></div>
<form id=\"frm_autoSchedule\" name=\"autoScheduleForm\" action=\"/admin/\" style=\"margin-top:15px\">
Modified: CalendarServer/trunk/conf/auth/accounts-test.xml
===================================================================
--- CalendarServer/trunk/conf/auth/accounts-test.xml 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/auth/accounts-test.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+Copyright (c) 2006-2010 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -52,27 +52,18 @@
<name>Public %02d</name>
<first-name>Public</first-name>
<last-name>%02d</last-name>
- <email-address>public%02d at example.com</email-address>
</user>
<location repeat="10">
<uid>location%02d</uid>
<guid>location%02d</guid>
<password>location%02d</password>
<name>Room %02d</name>
- <auto-schedule/>
</location>
<resource repeat="10">
<uid>resource%02d</uid>
<guid>resource%02d</guid>
<password>resource%02d</password>
<name>Resource %02d</name>
- <auto-schedule/>
- <proxies>
- <member type="users">user01</member>
- </proxies>
- <read-only-proxies>
- <member type="users">user03</member>
- </read-only-proxies>
</resource>
<group>
<uid>group01</uid>
@@ -122,6 +113,5 @@
<members>
<member type="users">user01</member>
</members>
- <disable-calendar/>
</group>
</accounts>
Modified: CalendarServer/trunk/conf/auth/accounts.dtd
===================================================================
--- CalendarServer/trunk/conf/auth/accounts.dtd 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/auth/accounts.dtd 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
<!--
-Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+Copyright (c) 2006-2010 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,16 +17,16 @@
<!ELEMENT accounts (user*, group*, resource*, location*) >
<!ATTLIST accounts realm CDATA "">
- <!ELEMENT user (uid+, guid, password, name, first-name?, last-name?, email-address*, disable-calendar?)>
+ <!ELEMENT user (uid*, guid, password?, name?, first-name?, last-name?, email-address*)>
<!ATTLIST user repeat CDATA "1">
- <!ELEMENT group (uid+, guid, password, name, members, disable-calendar?)>
+ <!ELEMENT group (uid*, guid, password?, name?, members)>
<!ATTLIST group repeat CDATA "1">
- <!ELEMENT resource (uid+, guid, password, name, auto-schedule?, proxies?, read-only-proxies?)>
+ <!ELEMENT resource (uid*, guid, password?, name?,)>
<!ATTLIST resource repeat CDATA "1">
- <!ELEMENT location (uid+, guid, password, name, auto-schedule?, proxies?, read-only-proxies?)>
+ <!ELEMENT location (uid*, guid, password?, name?,)>
<!ATTLIST location repeat CDATA "1">
<!ELEMENT member (#PCDATA)>
@@ -36,9 +36,8 @@
<!ELEMENT guid (#PCDATA)>
<!ELEMENT password (#PCDATA)>
<!ELEMENT name (#PCDATA)>
+ <!ELEMENT first-name (#PCDATA)>
+ <!ELEMENT last-name (#PCDATA)>
+ <!ELEMENT email-address (#PCDATA)>
<!ELEMENT members (member*)>
- <!ELEMENT auto-schedule EMPTY>
- <!ELEMENT disable-calendar EMPTY>
- <!ELEMENT proxies (member*)>
- <!ELEMENT read-only-proxies (member*)>
>
Deleted: CalendarServer/trunk/conf/auth/accounts.htauth
===================================================================
--- CalendarServer/trunk/conf/auth/accounts.htauth 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/auth/accounts.htauth 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,2 +0,0 @@
-admin:lzNnaQ7ZCPOlo
-test:m4cActsgkmlbI
Deleted: CalendarServer/trunk/conf/auth/accounts.htdigest
===================================================================
--- CalendarServer/trunk/conf/auth/accounts.htdigest 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/auth/accounts.htdigest 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,3 +0,0 @@
-
-admin:Test Realm:ffa04aeb43a8a2efbe653956e04b739f
-test:Test Realm:688067d58e519c828459eee8c9f65043
Modified: CalendarServer/trunk/conf/auth/accounts.xml
===================================================================
--- CalendarServer/trunk/conf/auth/accounts.xml 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/auth/accounts.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+Copyright (c) 2006-2010 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@
<uid>test</uid>
<password>test</password>
<name>Test User</name>
- <email-address>testuser at example.com</email-address>
</user>
<group>
<uid>users</uid>
@@ -42,10 +41,6 @@
<uid>mercury</uid>
<password>mercury</password>
<name>Mecury Conference Room, Building 1, 2nd Floor</name>
- <auto-schedule/>
- <proxies>
- <member type="users">test</member>
- </proxies>
</location>
</accounts>
Copied: CalendarServer/trunk/conf/auth/augments-test.xml (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/conf/auth/augments-test.xml)
===================================================================
--- CalendarServer/trunk/conf/auth/augments-test.xml (rev 0)
+++ CalendarServer/trunk/conf/auth/augments-test.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE augments SYSTEM "augments.dtd">
+
+<augments>
+ <record>
+ <guid>Default</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record repeat="10">
+ <guid>location%02d</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <auto-schedule>true</auto-schedule>
+ </record>
+ <record repeat="10">
+ <guid>resource%02d</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <auto-schedule>true</auto-schedule>
+ </record>
+ <record repeat="4">
+ <guid>group%02d</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>disabledgroup</guid>
+ <enable>false</enable>
+ </record>
+</augments>
Copied: CalendarServer/trunk/conf/auth/augments.dtd (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/conf/auth/augments.dtd)
===================================================================
--- CalendarServer/trunk/conf/auth/augments.dtd (rev 0)
+++ CalendarServer/trunk/conf/auth/augments.dtd 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,27 @@
+<!--
+Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<!ELEMENT augments (record*) >
+
+ <!ELEMENT record (guid, enable, hosted-at?, enable-calendar?, auto-schedule?)>
+ <!ATTLIST record repeat CDATA "1">
+
+ <!ELEMENT guid (#PCDATA)>
+ <!ELEMENT enable (#PCDATA)>
+ <!ELEMENT hosted-at (#PCDATA)>
+ <!ELEMENT enable-calendar (#PCDATA)>
+ <!ELEMENT auto-schedule (#PCDATA)>
+>
Copied: CalendarServer/trunk/conf/auth/proxies-test.xml (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/conf/auth/proxies-test.xml)
===================================================================
--- CalendarServer/trunk/conf/auth/proxies-test.xml (rev 0)
+++ CalendarServer/trunk/conf/auth/proxies-test.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE proxies SYSTEM "proxies.dtd">
+
+<proxies>
+ <record repeat="10">
+ <guid>resource%02d</guid>
+ <proxies>
+ <member>user01</member>
+ </proxies>
+ <read-only-proxies>
+ <member>user03</member>
+ </read-only-proxies>
+ </record>
+</proxies>
Copied: CalendarServer/trunk/conf/auth/proxies.dtd (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/conf/auth/proxies.dtd)
===================================================================
--- CalendarServer/trunk/conf/auth/proxies.dtd (rev 0)
+++ CalendarServer/trunk/conf/auth/proxies.dtd 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,26 @@
+<!--
+Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<!ELEMENT proxies (record*) >
+
+ <!ELEMENT record (guid, proxies?, read-only-proxies?)>
+ <!ATTLIST record repeat CDATA "1">
+
+ <!ELEMENT guid (#PCDATA)>
+ <!ELEMENT proxies (member*)>
+ <!ELEMENT read-only-proxies (member*)>
+ <!ELEMENT member (#PCDATA)>
+>
Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/caldavd-apple.plist 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+ Copyright (c) 2006-2010 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -160,6 +160,92 @@
<!--
+ Augment service
+
+ Augments for the directory service records to add calendar specific attributes.
+
+ A variety of augment services are available for use.
+ When using a partitioned server, a service that can be accessed from each host will be needed.
+ -->
+
+ <!-- XML File Augment Service -->
+ <key>AugmentService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>xmlFiles</key>
+ <array>
+ <string>/etc/caldavd/augments.xml</string>
+ </array>
+ </dict>
+ </dict>
+
+ <!-- Sqlite Augment Service -->
+ <!--
+ <key>AugmentService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbpath</key>
+ <string>/etc/caldavd/augments.sqlite</string>
+ </dict>
+ </dict>
+ -->
+
+ <!-- PostgreSQL Augment Service -->
+ <!--
+ <key>AugmentService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>host</key>
+ <string>localhost</string>
+ <key>database</key>
+ <string>augments</string>
+ </dict>
+ </dict>
+ -->
+
+ <!-- Sqlite ProxyDB Service -->
+ <key>ProxyDBService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbpath</key>
+ <string>/etc/caldavd/proxies.sqlite</string>
+ </dict>
+ </dict>
+
+ <!-- PostgreSQL ProxyDB Service -->
+ <!--
+ <key>ProxyDBService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>host</key>
+ <string>localhost</string>
+ <key>database</key>
+ <string>proxies</string>
+ </dict>
+ </dict>
+ -->
+
+ <!--
Special principals
These principals are granted special access and/or perform
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+ Copyright (c) 2006-2010 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -145,71 +145,100 @@
<dict>
<key>node</key>
<string>/Search</string>
- <key>restrictEnabledRecords</key>
- <false/>
- <key>restrictToGroup</key>
- <string></string>
<key>cacheTimeout</key>
<integer>30</integer>
</dict>
</dict>
-->
- <!-- Apache-style Basic Directory Service (Experimental) -->
<!--
- <key>DirectoryService</key>
+ Augment service
+
+ Augments for the directory service records to add calendar specific attributes.
+
+ A variety of augment services are available for use.
+ When using a partitioned server, a service that can be accessed from each host will be needed.
+ -->
+
+ <!-- XML File Augment Service -->
+ <key>AugmentService</key>
<dict>
<key>type</key>
- <string>twistedcaldav.directory.apache.BasicDirectoryService</string>
+ <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
<key>params</key>
<dict>
- <key>realmName</key>
- <string>Test Realm</string>
- <key>userFile</key>
- <string>conf/auth/accounts.htauth</string>
- <key>groupFile</key>
- <string>conf/auth/groups</string>
+ <key>xmlFiles</key>
+ <array>
+ <string>conf/auth/augments-test.xml</string>
+ </array>
</dict>
</dict>
- -->
+
+ <!-- Sqlite Augment Service -->
+ <!--
+ <key>AugmentService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbpath</key>
+ <string>/etc/caldavd/augments.sqlite</string>
+ </dict>
+ </dict>
+ -->
- <!-- Apache-style Digest Directory Service (Experimental) -->
+ <!-- PostgreSQL Augment Service -->
<!--
- <key>DirectoryService</key>
+ <key>AugmentService</key>
<dict>
<key>type</key>
- <string>twistedcaldav.directory.apache.DigestDirectoryService</string>
+ <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
<key>params</key>
<dict>
- <key>realmName</key>
- <string>Test Realm</string>
- <key>userFile</key>
- <string>conf/auth/accounts.htdigest</string>
- <key>groupFile</key>
- <string>conf/auth/groups</string>
+ <key>host</key>
+ <string>localhost</string>
+ <key>database</key>
+ <string>augments</string>
</dict>
</dict>
-->
- <!-- SQL Directory Service (Experimental) -->
+ <!-- Sqlite ProxyDB Service -->
+ <key>ProxyDBService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbpath</key>
+ <string>data/proxies.sqlite</string>
+ </dict>
+ </dict>
+
+ <!-- PostgreSQL ProxyDB Service -->
<!--
- <key>DirectoryService</key>
+ <key>ProxyDBService</key>
<dict>
<key>type</key>
- <string>twistedcaldav.directory.sqldb.SQLDirectoryService</string>
+ <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
<key>params</key>
<dict>
- <key>dbParentPath</key>
- <string>twistedcaldav/test/data/</string>
- <key>xmlFile</key>
- <string>conf/auth/accounts-test.xml</string>
+ <key>host</key>
+ <string>localhost</string>
+ <key>database</key>
+ <string>proxies</string>
</dict>
</dict>
- -->
+ -->
+ <key>ProxyLoadFromFile</key>
+ <string>conf/auth/proxies-test.xml</string>
<!--
Special principals
@@ -655,10 +684,6 @@
<!-- Support for Memcached -->
<key>Memcached</key>
<dict>
- <key>ServerEnabled</key>
- <true/>
- <key>ClientEnabled</key>
- <true/>
<key>MaxClients</key>
<integer>5</integer>
<key>memcached</key>
Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/conf/caldavd.plist 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+ Copyright (c) 2006-2010 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -148,10 +148,6 @@
<dict>
<key>node</key>
<string>/Search</string>
- <key>restrictEnabledRecords</key>
- <false/>
- <key>restrictToGroup</key>
- <string></string>
<key>cacheTimeout</key>
<integer>30</integer>
</dict>
@@ -159,6 +155,92 @@
-->
<!--
+ Augment service
+
+ Augments for the directory service records to add calendar specific attributes.
+
+ A variety of augment services are available for use.
+ When using a partitioned server, a service that can be accessed from each host will be needed.
+ -->
+
+ <!-- XML File Augment Service -->
+ <key>AugmentService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>xmlFiles</key>
+ <array>
+ <string>/etc/caldavd/augments.xml</string>
+ </array>
+ </dict>
+ </dict>
+
+ <!-- Sqlite Augment Service -->
+ <!--
+ <key>AugmentService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbpath</key>
+ <string>/etc/caldavd/augments.sqlite</string>
+ </dict>
+ </dict>
+ -->
+
+ <!-- PostgreSQL Augment Service -->
+ <!--
+ <key>AugmentService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>host</key>
+ <string>localhost</string>
+ <key>database</key>
+ <string>augments</string>
+ </dict>
+ </dict>
+ -->
+
+ <!-- Sqlite ProxyDB Service -->
+ <key>ProxyDBService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbpath</key>
+ <string>/etc/caldavd/proxies.sqlite</string>
+ </dict>
+ </dict>
+
+ <!-- PostgreSQL ProxyDB Service -->
+ <!--
+ <key>ProxyDBService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
+
+ <key>params</key>
+ <dict>
+ <key>host</key>
+ <string>localhost</string>
+ <key>database</key>
+ <string>proxies</string>
+ </dict>
+ </dict>
+ -->
+
+ <!--
Special principals
These principals are granted special access and/or perform
Copied: CalendarServer/trunk/conf/partitions-test.plist (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/conf/partitions-test.plist)
===================================================================
--- CalendarServer/trunk/conf/partitions-test.plist (rev 0)
+++ CalendarServer/trunk/conf/partitions-test.plist 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>partitions</key>
+ <array>
+ <dict>
+ <key>uid</key>
+ <string>00001</string>
+ <key>url</key>
+ <string>http://localhost:8008</string>
+ </dict>
+ <dict>
+ <key>uid</key>
+ <string>00002</string>
+ <key>url</key>
+ <string>http://localhost:8108</string>
+ </dict>
+ </array>
+</dict>
+</plist>
Copied: CalendarServer/trunk/conf/partitions.plist (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/conf/partitions.plist)
===================================================================
--- CalendarServer/trunk/conf/partitions.plist (rev 0)
+++ CalendarServer/trunk/conf/partitions.plist 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>partitions</key>
+ <array>
+ <!--
+ <dict>
+ <key>uid</key>
+ <string>00001</string>
+ <key>url</key>
+ <string>http://localhost:8008</string>
+ </dict>
+ -->
+ </array>
+</dict>
+</plist>
Modified: CalendarServer/trunk/contrib/migration/59_calendarmigrator.py
===================================================================
--- CalendarServer/trunk/contrib/migration/59_calendarmigrator.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/contrib/migration/59_calendarmigrator.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -10,7 +10,7 @@
# The only argument this script currently cares about is --sourceRoot, which
# should point to the root of the previous system.
#
-# Copyright (c) 2005-2009 Apple Inc. All Rights Reserved.
+# Copyright (c) 2005-2010 Apple Inc. All Rights Reserved.
#
# IMPORTANT NOTE: This file is licensed only for use on Apple-labeled
# computers and is subject to the terms and conditions of the Apple
@@ -187,7 +187,7 @@
newPlist["DirectoryService"] = oldPlist["DirectoryService"]
for key in newPlist["DirectoryService"]["params"].keys():
if key not in (
- "node", "restrictEnabledRecords", "restrictToGroup",
+ "node",
"cacheTimeout", "xmlFile"
):
del newPlist["DirectoryService"]["params"][key]
Property changes on: CalendarServer/trunk/doc/Extensions/caldav-privatecomments.txt
___________________________________________________________________
Added: svn:executable
+ *
Property changes on: CalendarServer/trunk/doc/Extensions/caldav-privatecomments.xml
___________________________________________________________________
Added: svn:executable
+ *
Modified: CalendarServer/trunk/memcacheclient.py
===================================================================
--- CalendarServer/trunk/memcacheclient.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/memcacheclient.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -124,7 +124,7 @@
pickler=pickle.Pickler, unpickler=pickle.Unpickler,
pload=None, pid=None):
- if config.Memcached.ClientEnabled:
+ if config.Memcached.Pools.Default.ClientEnabled:
return Client(servers, debug=debug, pickleProtocol=pickleProtocol,
pickler=pickler, unpickler=unpickler, pload=pload, pid=pid)
elif cls.allowTestCache:
Modified: CalendarServer/trunk/support/build.sh
===================================================================
--- CalendarServer/trunk/support/build.sh 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/support/build.sh 2010-01-25 15:56:33 UTC (rev 4958)
@@ -466,6 +466,10 @@
true false false false 0;
fi;
+ py_dependency "PyGreSQL" "pgdb" "PyGreSQL-4.0" \
+ "www" "ftp://ftp.pygresql.org/pub/distrib/PyGreSQL.tgz" \
+ false false false false 0;
+
py_dependency "Twisted" "twisted" "Twisted" \
"svn" "svn://svn.twistedmatrix.com/svn/Twisted/branches/dav-take-two-3081-4" \
false true true false 27622;
Modified: CalendarServer/trunk/twistedcaldav/cache.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/cache.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/cache.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
from twisted.web2.stream import MemoryStream
from twistedcaldav.log import LoggingMixIn
-from twistedcaldav.memcachepool import CachePoolUserMixIn
+from twistedcaldav.memcachepool import CachePoolUserMixIn, defaultCachePool
from twistedcaldav.config import config
@@ -61,9 +61,10 @@
class MemcacheChangeNotifier(LoggingMixIn, CachePoolUserMixIn):
- def __init__(self, resource, cachePool=None):
+ def __init__(self, resource, cachePool=None, cacheHandle="Default"):
self._resource = resource
self._cachePool = cachePool
+ self._cachePoolHandle = cacheHandle
def _newCacheToken(self):
return str(uuid.uuid4())
@@ -162,7 +163,7 @@
self._cachePool = cachePool
- def _tokenForURI(self, uri):
+ def _tokenForURI(self, uri, cachePoolHandle=None):
"""
Get a property store for the given C{uri}.
@@ -170,13 +171,16 @@
@return: A C{str} representing the token for the URI.
"""
- return self.getCachePool().get('cacheToken:%s' % (uri,))
+ if cachePoolHandle:
+ return defaultCachePool(cachePoolHandle).get('cacheToken:%s' % (uri,))
+ else:
+ return self.getCachePool().get('cacheToken:%s' % (uri,))
def _getTokens(self, request):
def _tokensForURIs((pURI, rURI)):
tokens = []
- d1 = self._tokenForURI(pURI)
+ d1 = self._tokenForURI(pURI, "PrincipalToken")
d1.addCallback(tokens.append)
d1.addCallback(lambda _ign: self._getRecordForURI(pURI, request))
d1.addCallback(lambda dToken: tokens.append(hash(dToken)))
Deleted: CalendarServer/trunk/twistedcaldav/client/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/client/__init__.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/client/__init__.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,16 +0,0 @@
-##
-# Copyright (c) 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.
-##
-
Copied: CalendarServer/trunk/twistedcaldav/client/__init__.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/client/__init__.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/client/__init__.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/client/__init__.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,16 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
Deleted: CalendarServer/trunk/twistedcaldav/client/pool.py
===================================================================
--- CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/client/pool.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/client/pool.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,391 +0,0 @@
-##
-# Copyright (c) 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.
-##
-
-__all__ = [
- "installPools",
- "installPool",
- "getHTTPClientPool",
-]
-
-from twext.internet.ssl import ChainingOpenSSLContextFactory
-from twisted.internet.address import IPv4Address
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
-from twisted.internet.error import ConnectionLost, ConnectionDone, ConnectError
-from twisted.internet.protocol import ClientFactory
-from twisted.web2 import responsecode
-from twisted.web2.client.http import HTTPClientProtocol
-from twisted.web2.http import StatusResponse, HTTPError
-from twistedcaldav.config import config
-from twistedcaldav.log import LoggingMixIn
-import OpenSSL
-import urlparse
-
-class PooledHTTPClientFactory(ClientFactory, LoggingMixIn):
- """
- A client factory for HTTPClient that notifies a pool of it's state. It the connection
- fails in the middle of a request it will retry the request.
-
- @ivar protocol: The current instance of our protocol that we pass
- to our connectionPool.
- @ivar connectionPool: A managing connection pool that we notify of events.
- """
- protocol = HTTPClientProtocol
- connectionPool = None
-
- def __init__(self, reactor):
- self.reactor = reactor
- self.instance = None
- self.onConnect = Deferred()
- self.afterConnect = Deferred()
-
- def clientConnectionLost(self, connector, reason):
- """
- Notify the connectionPool that we've lost our connection.
- """
-
- if hasattr(self, "afterConnect"):
- self.reactor.callLater(0, self.afterConnect.errback, reason)
- del self.afterConnect
-
- if self.connectionPool.shutdown_requested:
- # The reactor is stopping; don't reconnect
- return
-
- def clientConnectionFailed(self, connector, reason):
- """
- Notify the connectionPool that we're unable to connect
- """
- if hasattr(self, "onConnect"):
- self.reactor.callLater(0, self.onConnect.errback, reason)
- del self.onConnect
- elif hasattr(self, "afterConnect"):
- self.reactor.callLater(0, self.afterConnect.errback, reason)
- del self.afterConnect
-
- def buildProtocol(self, addr):
- self.instance = self.protocol()
- self.reactor.callLater(0, self.onConnect.callback, self.instance)
- del self.onConnect
- return self.instance
-
-class HTTPClientPool(LoggingMixIn):
- """
- A connection pool for HTTPClientProtocol instances.
-
- @ivar clientFactory: The L{ClientFactory} implementation that will be used
- for each protocol.
-
- @ivar _maxClients: A C{int} indicating the maximum number of clients.
- @ivar _serverAddress: An L{IAddress} provider indicating the server to
- connect to. (Only L{IPv4Address} currently supported.)
- @ivar _reactor: The L{IReactorTCP} provider used to initiate new
- connections.
-
- @ivar _busyClients: A C{set} that contains all currently busy clients.
- @ivar _freeClients: A C{set} that contains all currently free clients.
- @ivar _pendingConnects: A C{int} indicating how many connections are in
- progress.
- """
- clientFactory = PooledHTTPClientFactory
- maxRetries = 2
-
- def __init__(self, name, scheme, serverAddress, maxClients=5, reactor=None):
- """
- @param serverAddress: An L{IPv4Address} indicating the server to
- connect to.
- @param maxClients: A C{int} indicating the maximum number of clients.
- @param reactor: An L{IReactorTCP{ provider used to initiate new
- connections.
- """
-
- self._name = name
- self._scheme = scheme
- self._serverAddress = serverAddress
- self._maxClients = maxClients
-
- if reactor is None:
- from twisted.internet import reactor
- self._reactor = reactor
-
- self.shutdown_deferred = None
- self.shutdown_requested = False
- reactor.addSystemEventTrigger('before', 'shutdown', self._shutdownCallback)
-
- self._busyClients = set([])
- self._freeClients = set([])
- self._pendingConnects = 0
- self._pendingRequests = []
-
- def _isIdle(self):
- return (
- len(self._busyClients) == 0 and
- len(self._pendingRequests) == 0 and
- self._pendingConnects == 0
- )
-
- def _shutdownCallback(self):
- self.shutdown_requested = True
- if self._isIdle():
- return None
- self.shutdown_deferred = Deferred()
- return self.shutdown_deferred
-
- def _newClientConnection(self):
- """
- Create a new client connection.
-
- @return: A L{Deferred} that fires with the L{IProtocol} instance.
- """
- self.log_debug("Initiating new client connection to: %s" % (self._serverAddress,))
- self._logClientStats()
-
- self._pendingConnects += 1
-
- factory = self.clientFactory(self._reactor)
- factory.connectionPool = self
-
- if self._scheme == "https":
- context = ChainingOpenSSLContextFactory(config.SSLPrivateKey, config.SSLCertificate, certificateChainFile=config.SSLAuthorityChain, sslmethod=getattr(OpenSSL.SSL, config.SSLMethod))
- self._reactor.connectSSL(self._serverAddress.host, self._serverAddress.port, factory, context)
- elif self._scheme == "http":
- self._reactor.connectTCP(self._serverAddress.host, self._serverAddress.port, factory)
- else:
- raise ValueError("URL scheme for client pool not supported")
-
- def _doneOK(client):
- self._pendingConnects -= 1
-
- def _goneClientAfterError(f, client):
- f.trap(ConnectionLost, ConnectionDone, ConnectError)
- self.clientGone(client)
-
- d2 = factory.afterConnect
- d2.addErrback(_goneClientAfterError, client)
- return client
-
- def _doneError(result):
- self._pendingConnects -= 1
- return result
-
- d = factory.onConnect
- d.addCallbacks(_doneOK, _doneError)
-
- return d
-
- def _performRequestOnClient(self, client, request, *args, **kwargs):
- """
- Perform the given request on the given client.
-
- @param client: A L{PooledMemCacheProtocol} that will be used to perform
- the given request.
-
- @param command: A C{str} representing an attribute of
- L{MemCacheProtocol}.
- @parma args: Any positional arguments that should be passed to
- C{command}.
- @param kwargs: Any keyword arguments that should be passed to
- C{command}.
-
- @return: A L{Deferred} that fires with the result of the given command.
- """
-
- def _freeClientAfterRequest(result):
- self.clientFree(client)
- return result
-
- def _goneClientAfterError(result):
- self.clientGone(client)
- return result
-
- self.clientBusy(client)
- d = client.submitRequest(request, closeAfter=False)
- d.addCallbacks(_freeClientAfterRequest, _goneClientAfterError)
- return d
-
- @inlineCallbacks
- def submitRequest(self, request, *args, **kwargs):
- """
- Select an available client and perform the given request on it.
-
- @param command: A C{str} representing an attribute of
- L{MemCacheProtocol}.
- @parma args: Any positional arguments that should be passed to
- C{command}.
- @param kwargs: Any keyword arguments that should be passed to
- C{command}.
-
- @return: A L{Deferred} that fires with the result of the given command.
- """
-
- # Try this maxRetries times
- for ctr in xrange(self.maxRetries + 1):
- try:
- response = (yield self._submitRequest(request, args, kwargs))
-
- except (ConnectionLost, ConnectionDone, ConnectError), e:
- self.log_error("HTTP pooled client connection error (attempt: %d) - retrying: %s" % (ctr+1, e,))
- continue
-
- # TODO: find the proper cause of these assertions and fix
- except (AssertionError,), e:
- self.log_error("HTTP pooled client connection assertion error (attempt: %d) - retrying: %s" % (ctr+1, e,))
- continue
-
- else:
- returnValue(response)
- else:
- self.log_error("HTTP pooled client connection error - exhausted retry attempts.")
- raise HTTPError(StatusResponse(responsecode.BAD_GATEWAY, "Could not connect to HTTP pooled client host."))
-
- def _submitRequest(self, request, *args, **kwargs):
- """
- Select an available client and perform the given request on it.
-
- @param command: A C{str} representing an attribute of
- L{MemCacheProtocol}.
- @parma args: Any positional arguments that should be passed to
- C{command}.
- @param kwargs: Any keyword arguments that should be passed to
- C{command}.
-
- @return: A L{Deferred} that fires with the result of the given command.
- """
-
- if len(self._freeClients) > 0:
- d = self._performRequestOnClient(self._freeClients.pop(), request, *args, **kwargs)
-
- elif len(self._busyClients) + self._pendingConnects >= self._maxClients:
- d = Deferred()
- self._pendingRequests.append((d, request, args, kwargs))
- self.log_debug("Request queued: %s, %r, %r" % (request, args, kwargs))
- self._logClientStats()
-
- else:
- d = self._newClientConnection()
- d.addCallback(self._performRequestOnClient, request, *args, **kwargs)
-
- return d
-
- def _logClientStats(self):
- self.log_debug("Clients #free: %d, #busy: %d, "
- "#pending: %d, #queued: %d" % (
- len(self._freeClients),
- len(self._busyClients),
- self._pendingConnects,
- len(self._pendingRequests)))
-
- def clientGone(self, client):
- """
- Notify that the given client is to be removed from the pool completely.
-
- @param client: An instance of L{PooledMemCacheProtocol}.
- """
- if client in self._busyClients:
- self._busyClients.remove(client)
-
- elif client in self._freeClients:
- self._freeClients.remove(client)
-
- self.log_debug("Removed client: %r" % (client,))
- self._logClientStats()
-
- self._processPending()
-
- def clientBusy(self, client):
- """
- Notify that the given client is being used to complete a request.
-
- @param client: An instance of C{self.clientFactory}
- """
-
- if client in self._freeClients:
- self._freeClients.remove(client)
-
- self._busyClients.add(client)
-
- self.log_debug("Busied client: %r" % (client,))
- self._logClientStats()
-
- def clientFree(self, client):
- """
- Notify that the given client is free to handle more requests.
-
- @param client: An instance of C{self.clientFactory}
- """
- if client in self._busyClients:
- self._busyClients.remove(client)
-
- self._freeClients.add(client)
-
- if self.shutdown_deferred and self._isIdle():
- self.shutdown_deferred.callback(None)
-
- self.log_debug("Freed client: %r" % (client,))
- self._logClientStats()
-
- self._processPending()
-
- def _processPending(self):
- if len(self._pendingRequests) > 0:
- d, request, args, kwargs = self._pendingRequests.pop(0)
-
- self.log_debug("Performing Queued Request: %s, %r, %r" % (
- request, args, kwargs))
- self._logClientStats()
-
- _ign_d = self._submitRequest(request, *args, **kwargs)
-
- _ign_d.addCallbacks(d.callback, d.errback)
-
- def suggestMaxClients(self, maxClients):
- """
- Suggest the maximum number of concurrently connected clients.
-
- @param maxClients: A C{int} indicating how many client connections we
- should keep open.
- """
- self._maxClients = maxClients
-
-_clientPools = {} # Maps a host:port to a pool object
-
-def installPools(hosts, maxClients=5, reactor=None):
-
- for name, url in hosts:
- installPool(
- name,
- url,
- maxClients,
- reactor,
- )
-
-def installPool(name, url, maxClients=5, reactor=None):
-
- parsedURL = urlparse.urlparse(url)
- pool = HTTPClientPool(
- name,
- parsedURL.scheme,
- IPv4Address(
- "TCP",
- parsedURL.hostname,
- parsedURL.port,
- ),
- maxClients,
- reactor,
- )
- _clientPools[name] = pool
-
-def getHTTPClientPool(name):
- return _clientPools[name]
Copied: CalendarServer/trunk/twistedcaldav/client/pool.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/client/pool.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/client/pool.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/client/pool.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,391 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+__all__ = [
+ "installPools",
+ "installPool",
+ "getHTTPClientPool",
+]
+
+from twext.internet.ssl import ChainingOpenSSLContextFactory
+from twisted.internet.address import IPv4Address
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
+from twisted.internet.error import ConnectionLost, ConnectionDone, ConnectError
+from twisted.internet.protocol import ClientFactory
+from twisted.web2 import responsecode
+from twisted.web2.client.http import HTTPClientProtocol
+from twisted.web2.http import StatusResponse, HTTPError
+from twistedcaldav.config import config
+from twistedcaldav.log import LoggingMixIn
+import OpenSSL
+import urlparse
+
+class PooledHTTPClientFactory(ClientFactory, LoggingMixIn):
+ """
+ A client factory for HTTPClient that notifies a pool of it's state. It the connection
+ fails in the middle of a request it will retry the request.
+
+ @ivar protocol: The current instance of our protocol that we pass
+ to our connectionPool.
+ @ivar connectionPool: A managing connection pool that we notify of events.
+ """
+ protocol = HTTPClientProtocol
+ connectionPool = None
+
+ def __init__(self, reactor):
+ self.reactor = reactor
+ self.instance = None
+ self.onConnect = Deferred()
+ self.afterConnect = Deferred()
+
+ def clientConnectionLost(self, connector, reason):
+ """
+ Notify the connectionPool that we've lost our connection.
+ """
+
+ if hasattr(self, "afterConnect"):
+ self.reactor.callLater(0, self.afterConnect.errback, reason)
+ del self.afterConnect
+
+ if self.connectionPool.shutdown_requested:
+ # The reactor is stopping; don't reconnect
+ return
+
+ def clientConnectionFailed(self, connector, reason):
+ """
+ Notify the connectionPool that we're unable to connect
+ """
+ if hasattr(self, "onConnect"):
+ self.reactor.callLater(0, self.onConnect.errback, reason)
+ del self.onConnect
+ elif hasattr(self, "afterConnect"):
+ self.reactor.callLater(0, self.afterConnect.errback, reason)
+ del self.afterConnect
+
+ def buildProtocol(self, addr):
+ self.instance = self.protocol()
+ self.reactor.callLater(0, self.onConnect.callback, self.instance)
+ del self.onConnect
+ return self.instance
+
+class HTTPClientPool(LoggingMixIn):
+ """
+ A connection pool for HTTPClientProtocol instances.
+
+ @ivar clientFactory: The L{ClientFactory} implementation that will be used
+ for each protocol.
+
+ @ivar _maxClients: A C{int} indicating the maximum number of clients.
+ @ivar _serverAddress: An L{IAddress} provider indicating the server to
+ connect to. (Only L{IPv4Address} currently supported.)
+ @ivar _reactor: The L{IReactorTCP} provider used to initiate new
+ connections.
+
+ @ivar _busyClients: A C{set} that contains all currently busy clients.
+ @ivar _freeClients: A C{set} that contains all currently free clients.
+ @ivar _pendingConnects: A C{int} indicating how many connections are in
+ progress.
+ """
+ clientFactory = PooledHTTPClientFactory
+ maxRetries = 2
+
+ def __init__(self, name, scheme, serverAddress, maxClients=5, reactor=None):
+ """
+ @param serverAddress: An L{IPv4Address} indicating the server to
+ connect to.
+ @param maxClients: A C{int} indicating the maximum number of clients.
+ @param reactor: An L{IReactorTCP{ provider used to initiate new
+ connections.
+ """
+
+ self._name = name
+ self._scheme = scheme
+ self._serverAddress = serverAddress
+ self._maxClients = maxClients
+
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+ self.shutdown_deferred = None
+ self.shutdown_requested = False
+ reactor.addSystemEventTrigger('before', 'shutdown', self._shutdownCallback)
+
+ self._busyClients = set([])
+ self._freeClients = set([])
+ self._pendingConnects = 0
+ self._pendingRequests = []
+
+ def _isIdle(self):
+ return (
+ len(self._busyClients) == 0 and
+ len(self._pendingRequests) == 0 and
+ self._pendingConnects == 0
+ )
+
+ def _shutdownCallback(self):
+ self.shutdown_requested = True
+ if self._isIdle():
+ return None
+ self.shutdown_deferred = Deferred()
+ return self.shutdown_deferred
+
+ def _newClientConnection(self):
+ """
+ Create a new client connection.
+
+ @return: A L{Deferred} that fires with the L{IProtocol} instance.
+ """
+ self.log_debug("Initiating new client connection to: %s" % (self._serverAddress,))
+ self._logClientStats()
+
+ self._pendingConnects += 1
+
+ factory = self.clientFactory(self._reactor)
+ factory.connectionPool = self
+
+ if self._scheme == "https":
+ context = ChainingOpenSSLContextFactory(config.SSLPrivateKey, config.SSLCertificate, certificateChainFile=config.SSLAuthorityChain, sslmethod=getattr(OpenSSL.SSL, config.SSLMethod))
+ self._reactor.connectSSL(self._serverAddress.host, self._serverAddress.port, factory, context)
+ elif self._scheme == "http":
+ self._reactor.connectTCP(self._serverAddress.host, self._serverAddress.port, factory)
+ else:
+ raise ValueError("URL scheme for client pool not supported")
+
+ def _doneOK(client):
+ self._pendingConnects -= 1
+
+ def _goneClientAfterError(f, client):
+ f.trap(ConnectionLost, ConnectionDone, ConnectError)
+ self.clientGone(client)
+
+ d2 = factory.afterConnect
+ d2.addErrback(_goneClientAfterError, client)
+ return client
+
+ def _doneError(result):
+ self._pendingConnects -= 1
+ return result
+
+ d = factory.onConnect
+ d.addCallbacks(_doneOK, _doneError)
+
+ return d
+
+ def _performRequestOnClient(self, client, request, *args, **kwargs):
+ """
+ Perform the given request on the given client.
+
+ @param client: A L{PooledMemCacheProtocol} that will be used to perform
+ the given request.
+
+ @param command: A C{str} representing an attribute of
+ L{MemCacheProtocol}.
+ @parma args: Any positional arguments that should be passed to
+ C{command}.
+ @param kwargs: Any keyword arguments that should be passed to
+ C{command}.
+
+ @return: A L{Deferred} that fires with the result of the given command.
+ """
+
+ def _freeClientAfterRequest(result):
+ self.clientFree(client)
+ return result
+
+ def _goneClientAfterError(result):
+ self.clientGone(client)
+ return result
+
+ self.clientBusy(client)
+ d = client.submitRequest(request, closeAfter=False)
+ d.addCallbacks(_freeClientAfterRequest, _goneClientAfterError)
+ return d
+
+ @inlineCallbacks
+ def submitRequest(self, request, *args, **kwargs):
+ """
+ Select an available client and perform the given request on it.
+
+ @param command: A C{str} representing an attribute of
+ L{MemCacheProtocol}.
+ @parma args: Any positional arguments that should be passed to
+ C{command}.
+ @param kwargs: Any keyword arguments that should be passed to
+ C{command}.
+
+ @return: A L{Deferred} that fires with the result of the given command.
+ """
+
+ # Try this maxRetries times
+ for ctr in xrange(self.maxRetries + 1):
+ try:
+ response = (yield self._submitRequest(request, args, kwargs))
+
+ except (ConnectionLost, ConnectionDone, ConnectError), e:
+ self.log_error("HTTP pooled client connection error (attempt: %d) - retrying: %s" % (ctr+1, e,))
+ continue
+
+ # TODO: find the proper cause of these assertions and fix
+ except (AssertionError,), e:
+ self.log_error("HTTP pooled client connection assertion error (attempt: %d) - retrying: %s" % (ctr+1, e,))
+ continue
+
+ else:
+ returnValue(response)
+ else:
+ self.log_error("HTTP pooled client connection error - exhausted retry attempts.")
+ raise HTTPError(StatusResponse(responsecode.BAD_GATEWAY, "Could not connect to HTTP pooled client host."))
+
+ def _submitRequest(self, request, *args, **kwargs):
+ """
+ Select an available client and perform the given request on it.
+
+ @param command: A C{str} representing an attribute of
+ L{MemCacheProtocol}.
+ @parma args: Any positional arguments that should be passed to
+ C{command}.
+ @param kwargs: Any keyword arguments that should be passed to
+ C{command}.
+
+ @return: A L{Deferred} that fires with the result of the given command.
+ """
+
+ if len(self._freeClients) > 0:
+ d = self._performRequestOnClient(self._freeClients.pop(), request, *args, **kwargs)
+
+ elif len(self._busyClients) + self._pendingConnects >= self._maxClients:
+ d = Deferred()
+ self._pendingRequests.append((d, request, args, kwargs))
+ self.log_debug("Request queued: %s, %r, %r" % (request, args, kwargs))
+ self._logClientStats()
+
+ else:
+ d = self._newClientConnection()
+ d.addCallback(self._performRequestOnClient, request, *args, **kwargs)
+
+ return d
+
+ def _logClientStats(self):
+ self.log_debug("Clients #free: %d, #busy: %d, "
+ "#pending: %d, #queued: %d" % (
+ len(self._freeClients),
+ len(self._busyClients),
+ self._pendingConnects,
+ len(self._pendingRequests)))
+
+ def clientGone(self, client):
+ """
+ Notify that the given client is to be removed from the pool completely.
+
+ @param client: An instance of L{PooledMemCacheProtocol}.
+ """
+ if client in self._busyClients:
+ self._busyClients.remove(client)
+
+ elif client in self._freeClients:
+ self._freeClients.remove(client)
+
+ self.log_debug("Removed client: %r" % (client,))
+ self._logClientStats()
+
+ self._processPending()
+
+ def clientBusy(self, client):
+ """
+ Notify that the given client is being used to complete a request.
+
+ @param client: An instance of C{self.clientFactory}
+ """
+
+ if client in self._freeClients:
+ self._freeClients.remove(client)
+
+ self._busyClients.add(client)
+
+ self.log_debug("Busied client: %r" % (client,))
+ self._logClientStats()
+
+ def clientFree(self, client):
+ """
+ Notify that the given client is free to handle more requests.
+
+ @param client: An instance of C{self.clientFactory}
+ """
+ if client in self._busyClients:
+ self._busyClients.remove(client)
+
+ self._freeClients.add(client)
+
+ if self.shutdown_deferred and self._isIdle():
+ self.shutdown_deferred.callback(None)
+
+ self.log_debug("Freed client: %r" % (client,))
+ self._logClientStats()
+
+ self._processPending()
+
+ def _processPending(self):
+ if len(self._pendingRequests) > 0:
+ d, request, args, kwargs = self._pendingRequests.pop(0)
+
+ self.log_debug("Performing Queued Request: %s, %r, %r" % (
+ request, args, kwargs))
+ self._logClientStats()
+
+ _ign_d = self._submitRequest(request, *args, **kwargs)
+
+ _ign_d.addCallbacks(d.callback, d.errback)
+
+ def suggestMaxClients(self, maxClients):
+ """
+ Suggest the maximum number of concurrently connected clients.
+
+ @param maxClients: A C{int} indicating how many client connections we
+ should keep open.
+ """
+ self._maxClients = maxClients
+
+_clientPools = {} # Maps a host:port to a pool object
+
+def installPools(hosts, maxClients=5, reactor=None):
+
+ for name, url in hosts:
+ installPool(
+ name,
+ url,
+ maxClients,
+ reactor,
+ )
+
+def installPool(name, url, maxClients=5, reactor=None):
+
+ parsedURL = urlparse.urlparse(url)
+ pool = HTTPClientPool(
+ name,
+ parsedURL.scheme,
+ IPv4Address(
+ "TCP",
+ parsedURL.hostname,
+ parsedURL.port,
+ ),
+ maxClients,
+ reactor,
+ )
+ _clientPools[name] = pool
+
+def getHTTPClientPool(name):
+ return _clientPools[name]
Deleted: CalendarServer/trunk/twistedcaldav/client/reverseproxy.py
===================================================================
--- CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/client/reverseproxy.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/client/reverseproxy.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,76 +0,0 @@
-##
-# Copyright (c) 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.
-##
-
-__all__ = [
- "ReverseProxyResource",
-]
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web2 import iweb
-from twisted.web2.client.http import ClientRequest
-from twisted.web2.resource import LeafResource
-
-from twistedcaldav.client.pool import getHTTPClientPool
-from twistedcaldav.config import config
-from twistedcaldav.log import LoggingMixIn
-
-import urllib
-from zope.interface.declarations import implements
-
-class ReverseProxyResource(LeafResource, LoggingMixIn):
- """
- A L{LeafResource} which always performs a reverse proxy operation.
- """
- implements(iweb.IResource)
-
- def __init__(self, poolID, *args, **kwargs):
- """
-
- @param poolID: idenitifier of the pool to use
- @type poolID: C{str}
- """
-
- self.poolID = poolID
- self._args = args
- self._kwargs = kwargs
-
- def isCollection(self):
- return True
-
- def exists(self):
- return False
-
- def renderHTTP(self, request):
- """
- Do the reverse proxy request and return the response.
-
- @param request: the incoming request that needs to be proxied.
- @type request: L{Request}
-
- @return: Deferred L{Response}
- """
-
- self.logger.info("%s %s %s" % (request.method, urllib.unquote(request.uri), "HTTP/%s.%s" % request.clientproto))
- clientPool = getHTTPClientPool(self.poolID)
- proxyRequest = ClientRequest(request.method, request.uri, request.headers, request.stream)
-
- # Need x-forwarded-(for|host) headers. First strip any existing ones out, then add ours
- proxyRequest.headers.removeHeader("x-forwarded-host")
- proxyRequest.headers.removeHeader("x-forwarded-for")
- proxyRequest.headers.addRawHeader("x-forwarded-host", config.ServerHostName)
- proxyRequest.headers.addRawHeader("x-forwarded-for", request.remoteAddr.host)
-
- return clientPool.submitRequest(proxyRequest)
Copied: CalendarServer/trunk/twistedcaldav/client/reverseproxy.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/client/reverseproxy.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/client/reverseproxy.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/client/reverseproxy.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,75 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+__all__ = [
+ "ReverseProxyResource",
+]
+
+from twisted.web2 import iweb
+from twisted.web2.client.http import ClientRequest
+from twisted.web2.resource import LeafResource
+
+from twistedcaldav.client.pool import getHTTPClientPool
+from twistedcaldav.config import config
+from twistedcaldav.log import LoggingMixIn
+
+import urllib
+from zope.interface.declarations import implements
+
+class ReverseProxyResource(LeafResource, LoggingMixIn):
+ """
+ A L{LeafResource} which always performs a reverse proxy operation.
+ """
+ implements(iweb.IResource)
+
+ def __init__(self, poolID, *args, **kwargs):
+ """
+
+ @param poolID: idenitifier of the pool to use
+ @type poolID: C{str}
+ """
+
+ self.poolID = poolID
+ self._args = args
+ self._kwargs = kwargs
+
+ def isCollection(self):
+ return True
+
+ def exists(self):
+ return False
+
+ def renderHTTP(self, request):
+ """
+ Do the reverse proxy request and return the response.
+
+ @param request: the incoming request that needs to be proxied.
+ @type request: L{Request}
+
+ @return: Deferred L{Response}
+ """
+
+ self.logger.info("%s %s %s" % (request.method, urllib.unquote(request.uri), "HTTP/%s.%s" % request.clientproto))
+ clientPool = getHTTPClientPool(self.poolID)
+ proxyRequest = ClientRequest(request.method, request.uri, request.headers, request.stream)
+
+ # Need x-forwarded-(for|host) headers. First strip any existing ones out, then add ours
+ proxyRequest.headers.removeHeader("x-forwarded-host")
+ proxyRequest.headers.removeHeader("x-forwarded-for")
+ proxyRequest.headers.addRawHeader("x-forwarded-host", config.ServerHostName)
+ proxyRequest.headers.addRawHeader("x-forwarded-for", request.remoteAddr.host)
+
+ return clientPool.submitRequest(proxyRequest)
Copied: CalendarServer/trunk/twistedcaldav/database.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/database.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/database.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/database.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,557 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.enterprise.adbapi import ConnectionPool
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.threadpool import ThreadPool
+
+from twistedcaldav.config import ConfigurationError
+from twistedcaldav.log import Logger
+
+import thread
+
+try:
+ import pgdb
+except:
+ pgdb = None
+#pgdb = None
+
+"""
+Generic ADAPI database access object.
+"""
+
+__all__ = [
+ "AbstractADBAPIDatabase",
+]
+
+log = Logger()
+
+class ConnectionClosingThreadPool(ThreadPool):
+ """
+ A ThreadPool that closes connections for each worker thread
+ """
+
+ def _worker(self):
+ log.debug("Starting ADBAPI thread: %s" % (thread.get_ident(),))
+ ThreadPool._worker(self)
+ self._closeConnection()
+
+ def _closeConnection(self):
+
+ tid = thread.get_ident()
+ log.debug("Closing ADBAPI thread: %s" % (tid,))
+
+ conn = self.pool.connections.get(tid)
+ self.pool._close(conn)
+ del self.pool.connections[tid]
+
+class AbstractADBAPIDatabase(object):
+ """
+ A generic SQL database.
+ """
+
+ def __init__(self, dbID, dbapiName, dbapiArgs, persistent, **kwargs):
+ """
+
+ @param persistent: C{True} if the data in the DB must be perserved during upgrades,
+ C{False} if the DB data can be re-created from an external source.
+ @type persistent: bool
+ """
+ self.dbID = dbID
+ self.dbapiName = dbapiName
+ self.dbapiArgs = dbapiArgs
+ self.dbapikwargs = kwargs
+
+ self.persistent = persistent
+
+ self.initialized = False
+
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.pool)
+
+ @inlineCallbacks
+ def open(self):
+ """
+ Access the underlying database.
+ @return: a db2 connection object for this index's underlying data store.
+ """
+ if not self.initialized:
+
+ self.pool = ConnectionPool(self.dbapiName, *self.dbapiArgs, **self.dbapikwargs)
+
+ # sqlite3 is not thread safe which means we have to close the sqlite3 connections in the same thread that
+ # opened them. We need a special thread pool class that has a thread worker function that does a close
+ # when a thread is closed.
+ if self.dbapiName == "sqlite3":
+ self.pool.threadpool.stop()
+ self.pool.threadpool = ConnectionClosingThreadPool(1, 1)
+ self.pool.threadpool.start()
+ self.pool.threadpool.pool = self.pool
+
+ #
+ # Set up the schema
+ #
+ # Create CALDAV table if needed
+
+ test = (yield self._test_schema_table())
+ if test:
+ version = (yield self._db_value_for_sql("select VALUE from CALDAV where KEY = 'SCHEMA_VERSION'"))
+ dbtype = (yield self._db_value_for_sql("select VALUE from CALDAV where KEY = 'TYPE'"))
+
+ if (version != self._db_version()) or (dbtype != self._db_type()):
+
+ if dbtype != self._db_type():
+ log.err("Database %s has different type (%s vs. %s)"
+ % (self.dbID, dbtype, self._db_type()))
+
+ # Delete this index and start over
+ yield self._db_remove()
+ yield self._db_init()
+
+ elif version != self._db_version():
+ log.err("Database %s has different schema (v.%s vs. v.%s)"
+ % (self.dbID, version, self._db_version()))
+
+ # Upgrade the DB
+ yield self._db_upgrade(version)
+
+ else:
+ yield self._db_init()
+ self.initialized = True
+
+ def close(self):
+
+ if self.initialized:
+ self.pool.close()
+ self.pool = None
+ self.initialized = False
+
+ @inlineCallbacks
+ def clean(self):
+
+ if not self.initialized:
+ yield self.open()
+
+ yield self._db_empty_data_tables()
+
+ @inlineCallbacks
+ def execute(self, sql, *query_params):
+
+ if not self.initialized:
+ yield self.open()
+
+ yield self._db_execute(sql, *query_params)
+
+ @inlineCallbacks
+ def executescript(self, script):
+
+ if not self.initialized:
+ yield self.open()
+
+ yield self._db_execute_script(script)
+
+ @inlineCallbacks
+ def query(self, sql, *query_params):
+
+ if not self.initialized:
+ yield self.open()
+
+ result = (yield self._db_all_values_for_sql(sql, *query_params))
+ returnValue(result)
+
+ @inlineCallbacks
+ def queryList(self, sql, *query_params):
+
+ if not self.initialized:
+ yield self.open()
+
+ result = (yield self._db_values_for_sql(sql, *query_params))
+ returnValue(result)
+
+ @inlineCallbacks
+ def queryOne(self, sql, *query_params):
+
+ if not self.initialized:
+ yield self.open()
+
+ result = (yield self._db_value_for_sql(sql, *query_params))
+ returnValue(result)
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this DB.
+ """
+ raise NotImplementedError
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this DB.
+ """
+ raise NotImplementedError
+
+ def _test_schema_table(self):
+ return self._test_table("CALDAV")
+
+ @inlineCallbacks
+ def _db_init(self):
+ """
+ Initialise the underlying database tables.
+ """
+ log.msg("Initializing database %s" % (self.dbID,))
+
+ # TODO we need an exclusive lock of some kind here to prevent a race condition
+ # in which multiple processes try to create the tables.
+
+
+ yield self._db_init_schema_table()
+ yield self._db_init_data_tables()
+ yield self._db_recreate()
+
+ @inlineCallbacks
+ def _db_init_schema_table(self):
+ """
+ Initialise the underlying database tables.
+ @param db_filename: the file name of the index database.
+ @param q: a database cursor to use.
+ """
+
+ #
+ # CALDAV table keeps track of our schema version and type
+ #
+ yield self._create_table("CALDAV", (
+ ("KEY", "text unique"),
+ ("VALUE", "text unique"),
+ ), True)
+
+ yield self._db_execute(
+ """
+ insert or ignore into CALDAV (KEY, VALUE)
+ values ('SCHEMA_VERSION', :1)
+ """, (self._db_version(),)
+ )
+ yield self._db_execute(
+ """
+ insert or ignore into CALDAV (KEY, VALUE)
+ values ('TYPE', :1)
+ """, (self._db_type(),)
+ )
+
+ def _db_init_data_tables(self):
+ """
+ Initialise the underlying database tables.
+ """
+ raise NotImplementedError
+
+ def _db_empty_data_tables(self):
+ """
+ Delete the database tables.
+ """
+
+ # Implementations can override this to re-create data
+ pass
+
+ def _db_recreate(self):
+ """
+ Recreate the database tables.
+ """
+
+ # Implementations can override this to re-create data
+ pass
+
+ @inlineCallbacks
+ def _db_upgrade(self, old_version):
+ """
+ Upgrade the database tables.
+ """
+
+ if self.persistent:
+ yield self._db_upgrade_data_tables(old_version)
+ yield self._db_upgrade_schema()
+ else:
+ # Non-persistent DB's by default can be removed and re-created. However, for simple
+ # DB upgrades they SHOULD override this method and handle those for better performance.
+ yield self._db_remove()
+ yield self._db_init()
+
+ def _db_upgrade_data_tables(self, old_version):
+ """
+ Upgrade the data from an older version of the DB.
+ """
+ # Persistent DB's MUST override this method and do a proper upgrade. Their data
+ # cannot be thrown away.
+ raise NotImplementedError("Persistent databases MUST support an upgrade method.")
+
+ @inlineCallbacks
+ def _db_upgrade_schema(self):
+ """
+ Upgrade the stored schema version to the current one.
+ """
+ yield self._db_execute("insert or replace into CALDAV (KEY, VALUE) values ('SCHEMA_VERSION', :1)", (self._db_version(),))
+
+ @inlineCallbacks
+ def _db_remove(self):
+ """
+ Remove all database information (all the tables)
+ """
+ yield self._db_remove_data_tables()
+ yield self._db_remove_schema()
+
+ def _db_remove_data_tables(self):
+ """
+ Remove all the data from an older version of the DB.
+ """
+ raise NotImplementedError("Each database must remove its own tables.")
+
+ @inlineCallbacks
+ def _db_remove_schema(self):
+ """
+ Remove the stored schema version table.
+ """
+ yield self._db_execute("drop table if exists CALDAV")
+
+ @inlineCallbacks
+ def _db_all_values_for_sql(self, sql, *query_params):
+ """
+ Execute an SQL query and obtain the resulting values.
+ @param sql: the SQL query to execute.
+ @param query_params: parameters to C{sql}.
+ @return: an interable of values in the first column of each row
+ resulting from executing C{sql} with C{query_params}.
+ @raise AssertionError: if the query yields multiple columns.
+ """
+
+ sql = self._prepare_statement(sql)
+ results = (yield self.pool.runQuery(sql, *query_params))
+ returnValue(tuple(results))
+
+ @inlineCallbacks
+ def _db_values_for_sql(self, sql, *query_params):
+ """
+ Execute an SQL query and obtain the resulting values.
+
+ @param sql: the SQL query to execute.
+ @param query_params: parameters to C{sql}.
+ @return: an interable of values in the first column of each row
+ resulting from executing C{sql} with C{query_params}.
+ @raise AssertionError: if the query yields multiple columns.
+ """
+
+ sql = self._prepare_statement(sql)
+ results = (yield self.pool.runQuery(sql, *query_params))
+ returnValue(tuple([row[0] for row in results]))
+
+ @inlineCallbacks
+ def _db_value_for_sql(self, sql, *query_params):
+ """
+ Execute an SQL query and obtain a single value.
+
+ @param sql: the SQL query to execute.
+ @param query_params: parameters to C{sql}.
+ @return: the value resulting from the executing C{sql} with
+ C{query_params}.
+ @raise AssertionError: if the query yields multiple rows or columns.
+ """
+ value = None
+ for row in (yield self._db_values_for_sql(sql, *query_params)):
+ assert value is None, "Multiple values in DB for %s %s" % (sql, query_params)
+ value = row
+ returnValue(value)
+
+ def _db_execute(self, sql, *query_params):
+ """
+ Execute an SQL operation that returns None.
+
+ @param sql: the SQL query to execute.
+ @param query_params: parameters to C{sql}.
+ @return: an iterable of tuples for each row resulting from executing
+ C{sql} with C{query_params}.
+ """
+
+ sql = self._prepare_statement(sql)
+ return self.pool.runOperation(sql, *query_params)
+
+ """
+ Since different databases support different types of columns and modifiers on those we need to
+ have an "abstract" way of specifying columns in our code and then map the abstract specifiers to
+ the underlying DB's allowed types.
+
+ Types we can use are:
+
+ integer
+ text
+ text(n)
+ date
+ serial
+
+ The " unique" modifier can be appended to any of those.
+ """
+ def _map_column_types(self, type):
+ raise NotImplementedError
+
+ def _create_table(self, name, columns, ifnotexists=False):
+ raise NotImplementedError
+
+ def _test_table(self, name):
+ raise NotImplementedError
+
+ def _prepare_statement(self, sql):
+ raise NotImplementedError
+
+class ADBAPISqliteMixin(object):
+
+ @classmethod
+ def _map_column_types(self, coltype):
+
+ result = ""
+ splits = coltype.split()
+ if splits[0] == "integer":
+ result = "integer"
+ elif splits[0] == "text":
+ result = "text"
+ elif splits[0].startswith("text("):
+ result = splits[0]
+ elif splits[0] == "date":
+ result = "date"
+ elif splits[0] == "serial":
+ result = "integer primary key autoincrement"
+
+ if len(splits) > 1 and splits[1] == "unique":
+ result += " unique"
+
+ return result
+
+ @inlineCallbacks
+ def _create_table(self, name, columns, ifnotexists=False):
+
+ colDefs = ["%s %s" % (colname, self._map_column_types(coltype)) for colname, coltype in columns]
+ statement = "create table %s%s (%s)" % (
+ "if not exists " if ifnotexists else "",
+ name,
+ ", ".join(colDefs),
+ )
+ yield self._db_execute(statement)
+
+ @inlineCallbacks
+ def _test_table(self, name):
+ result = (yield self._db_value_for_sql("""
+ select (1) from SQLITE_MASTER
+ where TYPE = 'table' and NAME = '%s'
+ """ % (name,)))
+ returnValue(result)
+
+ def _prepare_statement(self, sql):
+ # We are going to use the sqlite syntax of :1, :2 etc for our
+ # internal statements so we do not need to remap those
+ return sql
+
+if pgdb:
+
+ class ADBAPIPostgreSQLMixin(object):
+
+ @classmethod
+ def _map_column_types(self, coltype):
+
+ result = ""
+ splits = coltype.split()
+ if splits[0] == "integer":
+ result = "integer"
+ elif splits[0] == "text":
+ result = "text"
+ elif splits[0].startswith("text("):
+ result = "char" + splits[0][4:]
+ elif splits[0] == "date":
+ result = "date"
+ elif splits[0] == "serial":
+ result = "serial"
+
+ if len(splits) > 1 and splits[1] == "unique":
+ result += " unique"
+
+ return result
+
+ @inlineCallbacks
+ def _create_table(self, name, columns, ifnotexists=False):
+
+ colDefs = ["%s %s" % (colname, self._map_column_types(coltype)) for colname, coltype in columns]
+ statement = "create table %s (%s)" % (
+ name,
+ ", ".join(colDefs),
+ )
+
+ try:
+ yield self._db_execute(statement)
+ except pgdb.DatabaseError:
+
+ if not ifnotexists:
+ raise
+
+ result = (yield self._test_table(name))
+ if not result:
+ raise
+
+ @inlineCallbacks
+ def _test_table(self, name):
+ result = (yield self._db_value_for_sql("""
+ select * from pg_tables
+ where tablename = '%s'
+ """ % (name.lower(),)))
+ returnValue(result)
+
+ @inlineCallbacks
+ def _db_init_schema_table(self):
+ """
+ Initialise the underlying database tables.
+ @param db_filename: the file name of the index database.
+ @param q: a database cursor to use.
+ """
+
+ #
+ # CALDAV table keeps track of our schema version and type
+ #
+ try:
+ yield self._create_table("CALDAV", (
+ ("KEY", "text unique"),
+ ("VALUE", "text unique"),
+ ), True)
+
+ yield self._db_execute(
+ """
+ insert into CALDAV (KEY, VALUE)
+ values ('SCHEMA_VERSION', :1)
+ """, (self._db_version(),)
+ )
+ yield self._db_execute(
+ """
+ insert into CALDAV (KEY, VALUE)
+ values ('TYPE', :1)
+ """, (self._db_type(),)
+ )
+ except pgdb.DatabaseError:
+ pass
+
+ def _prepare_statement(self, sql):
+ # Convert :1, :2 etc format into %s
+ ctr = 1
+ while sql.find(":%d" % (ctr,)) != -1:
+ sql = sql.replace(":%d" % (ctr,), "%s")
+ ctr += 1
+ return sql
+
+else:
+ class ADBAPIPostgreSQLMixin(object):
+
+ def __init__(self):
+ raise ConfigurationError("PostgreSQL module not available.")
Deleted: CalendarServer/trunk/twistedcaldav/directory/apache.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/apache.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/apache.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,220 +0,0 @@
-##
-# Copyright (c) 2006-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.
-##
-
-"""
-Apache UserFile/GroupFile compatible directory service implementation.
-"""
-
-__all__ = [
- "BasicDirectoryService",
- "DigestDirectoryService",
-]
-
-from crypt import crypt
-
-from twisted.python.filepath import FilePath
-from twisted.cred.credentials import UsernamePassword
-
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.directory import UnknownRecordTypeError, DirectoryConfigurationError
-
-class AbstractDirectoryService(DirectoryService):
- """
- Abstract Apache-compatible implementation of L{IDirectoryService}.
- """
- def __repr__(self):
- return "<%s %r: %r %r>" % (self.__class__.__name__, self.realmName, self.userFile, self.groupFile)
-
- def __init__(self, params):
- defaults = {
- 'realmName' : '',
- 'userFile' : None,
- 'groupFile' : None,
- }
- ignored = None
- params = self.getParams(params, defaults, ignored)
-
- super(AbstractDirectoryService, self).__init__()
-
- userFile = params["userFile"]
- if not userFile:
- raise DirectoryConfigurationError("Invalid Apache user file name: %r" % (userFile,))
-
- if userFile and type(userFile) is str:
- userFile = FilePath(userFile)
-
- groupFile = params["groupFile"]
- if groupFile and type(groupFile) is str:
- groupFile = FilePath(groupFile)
-
- self.realmName = params["realmName"]
- self.userFile = userFile
- self.groupFile = groupFile
-
- def recordTypes(self):
- recordTypes = (DirectoryService.recordType_users,)
- if self.groupFile is not None:
- recordTypes += (DirectoryService.recordType_groups,)
- return recordTypes
-
- def listRecords(self, recordType):
- for entryShortName, entryData in self.entriesForRecordType(recordType):
- if recordType == DirectoryService.recordType_users:
- yield self.userRecordClass(
- service = self,
- recordType = recordType,
- shortName = entryShortName,
- cryptPassword = entryData,
- )
-
- elif recordType == DirectoryService.recordType_groups:
- yield GroupRecord(
- service = self,
- recordType = recordType,
- shortName = entryShortName,
- members = entryData,
- )
-
- else:
- # Subclass should cover the remaining record types
- raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
-
- def recordWithShortName(self, recordType, shortName):
- for entryShortName, entryData in self.entriesForRecordType(recordType):
- if entryShortName == shortName:
- if recordType == DirectoryService.recordType_users:
- return self.userRecordClass(
- service = self,
- recordType = recordType,
- shortName = entryShortName,
- cryptPassword = entryData,
- )
-
- if recordType == DirectoryService.recordType_groups:
- return GroupRecord(
- service = self,
- recordType = recordType,
- shortName = entryShortName,
- members = entryData,
- )
-
- # Subclass should cover the remaining record types
- raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
-
- return None
-
- def entriesForRecordType(self, recordType):
- if recordType == DirectoryService.recordType_users:
- recordFile = self.userFile
- elif recordType == DirectoryService.recordType_groups:
- recordFile = self.groupFile
- else:
- raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
-
- if recordFile is None:
- return
-
- try:
- handle = recordFile.open()
- except (IOError, OSError):
- self.log_error("Auth file (for %s) not found: %s" % (recordType, recordFile.path))
- return
-
- try:
- for entry in handle:
- if entry and entry[0] != "#":
- try:
- shortName, rest = entry.rstrip("\n").split(":", 1)
- except ValueError:
- continue
- yield shortName, rest
- finally:
- handle.close()
-
-class AbstractDirectoryRecord(DirectoryRecord):
- """
- Abstract Apache-compatible implementation of L{IDirectoryRecord}.
- """
- def __init__(self, service, recordType, shortName):
- super(AbstractDirectoryRecord, self).__init__(
- service = service,
- recordType = recordType,
- guid = None,
- shortNames = (shortName,),
- )
-
-class AbstractUserRecord(AbstractDirectoryRecord):
- def __init__(self, service, recordType, shortName, cryptPassword=None):
- super(AbstractUserRecord, self).__init__(service, recordType, shortName)
-
- self._cryptPassword = cryptPassword
-
- def groups(self):
- for group in self.service.listRecords(DirectoryService.recordType_groups):
- for member in group.members():
- if member == self:
- yield group
- continue
-
-class BasicUserRecord(AbstractUserRecord):
- """
- Apache UserFile implementation of L{IDirectoryRecord}.
- """
- def verifyCredentials(self, credentials):
- if self._cryptPassword in ("", "*", "x"):
- return False
-
- if isinstance(credentials, UsernamePassword):
- return crypt(credentials.password, self._cryptPassword) == self._cryptPassword
-
- return super(BasicUserRecord, self).verifyCredentials(credentials)
-
-class BasicDirectoryService(AbstractDirectoryService):
- """
- Apache UserFile/GroupFile implementation of L{IDirectoryService}.
- """
- baseGUID = "DDF1E45C-CADE-4FCD-8AE6-B4B41D72B325"
- userRecordClass = BasicUserRecord
-
-class DigestUserRecord(AbstractUserRecord):
- """
- Apache DigestUserFile implementation of L{IDirectoryRecord}.
- """
- def verifyCredentials(self, credentials):
- raise NotImplementedError()
-
-class DigestDirectoryService(AbstractDirectoryService):
- """
- Apache DigestUserFile/GroupFile implementation of L{IDirectoryService}.
- """
- baseGUID = "0C719D1B-0A14-4074-8740-6D96A7D0C787"
- userRecordClass = DigestUserRecord
-
-class GroupRecord(AbstractDirectoryRecord):
- """
- Apache GroupFile implementation of L{IDirectoryRecord}.
- """
- def __init__(self, service, recordType, shortName, members=()):
- super(GroupRecord, self).__init__(service, recordType, shortName)
-
- if type(members) is str:
- members = tuple(m.strip() for m in members.split(","))
-
- self._members = members
-
- def members(self):
- for shortName in self._members:
- yield self.service.recordWithShortName(DirectoryService.recordType_users, shortName)
Modified: CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -24,13 +24,7 @@
]
import sys
-import time
-from uuid import UUID
-from twext.python.plistlib import readPlistFromString
-
-from xml.parsers.expat import ExpatError
-
import opendirectory
import dsattributes
import dsquery
@@ -38,8 +32,9 @@
from twisted.internet.threads import deferToThread
from twisted.cred.credentials import UsernamePassword
from twisted.web2.auth.digest import DigestedCredentials
-from twistedcaldav.config import config
+from twistedcaldav.config import config
+from twistedcaldav.directory import augment
from twistedcaldav.directory.cachingdirectory import CachingDirectoryService,\
CachingDirectoryRecord
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
@@ -60,11 +55,6 @@
"""
@param params: a dictionary containing the following keys:
node: an OpenDirectory node name to bind to.
- restrictEnabledRecords: C{True} if a group in the
- directory is to be used to determine which calendar
- users are enabled.
- restrictToGroup: C{str} guid or name of group used to
- restrict enabled users.
cacheTimeout: C{int} number of minutes before cache is invalidated.
@param dosetup: if C{True} then the directory records are initialized,
if C{False} they are not.
@@ -73,11 +63,13 @@
defaults = {
'node' : '/Search',
- 'restrictEnabledRecords' : False,
- 'restrictToGroup' : '',
'cacheTimeout' : 30,
}
- ignored = ('requireComputerRecord',)
+ ignored = (
+ 'requireComputerRecord',
+ 'restrictEnabledRecords',
+ 'restrictToGroup'
+ )
params = self.getParams(params, defaults, ignored)
super(OpenDirectoryService, self).__init__(params['cacheTimeout'])
@@ -91,16 +83,6 @@
self.realmName = params['node']
self.directory = directory
self.node = params['node']
- self.restrictEnabledRecords = params['restrictEnabledRecords']
- self.restrictToGroup = params['restrictToGroup']
- try:
- UUID(self.restrictToGroup)
- except:
- self.restrictToGUID = False
- else:
- self.restrictToGUID = True
- self.restrictedGUIDs = None
- self.restrictedTimestamp = 0
self._records = {}
self._delayedCalls = set()
@@ -413,7 +395,6 @@
firstName = recordFirstName,
lastName = recordLastName,
emailAddresses = recordEmailAddresses,
- enabledForCalendaring = True,
memberGUIDs = (),
)
yield record
@@ -622,69 +603,6 @@
% (recordType, recordShortName, recordNodeName))
continue
- # Determine enabled state
- if recordType == self.recordType_groups:
- enabledForCalendaring = False
- else:
- if (
- self.restrictEnabledRecords and
- config.Scheduling.iMIP.Username != recordShortName
- ):
- if time.time() - self.restrictedTimestamp > self.cacheTimeout:
- attributeToMatch = dsattributes.kDS1AttrGeneratedUID if self.restrictToGUID else dsattributes.kDSNAttrRecordName
- valueToMatch = self.restrictToGroup
- self.log_debug("Doing restricted group membership check")
- self.log_debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
- self.directory,
- attributeToMatch,
- valueToMatch,
- dsattributes.eDSExact,
- False,
- dsattributes.kDSStdRecordTypeGroups,
- [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups,],
- ))
- results = lookupMethod(
- self.directory,
- attributeToMatch,
- valueToMatch,
- dsattributes.eDSExact,
- False,
- dsattributes.kDSStdRecordTypeGroups,
- [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups,],
- )
-
- if len(results) == 1:
- members = results[0][1].get(dsattributes.kDSNAttrGroupMembers, [])
- nestedGroups = results[0][1].get(dsattributes.kDSNAttrNestedGroups, [])
- else:
- members = []
- nestedGroups = []
- self.restrictedGUIDs = set(self._expandGroupMembership(members, nestedGroups, returnGroups=True))
- self.log_debug("Got %d restricted group members" % (len(self.restrictedGUIDs),))
- self.restrictedTimestamp = time.time()
-
- enabledForCalendaring = recordGUID in self.restrictedGUIDs
- else:
- enabledForCalendaring = True
-
- if not enabledForCalendaring:
- # Some records we want to keep even though they are not enabled for calendaring.
- # Others we discard.
- if recordType not in (
- self.recordType_users,
- self.recordType_groups,
- ):
- self.log_debug(
- "Record (%s) %s is not enabled for calendaring"
- % (recordType, recordShortName)
- )
- continue
-
- self.log_debug(
- "Record (%s) %s is not enabled for calendaring but may be used in ACLs"
- % (recordType, recordShortName)
- )
-
# Special case for groups, which have members.
if recordType == self.recordType_groups:
memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
@@ -711,10 +629,16 @@
firstName = recordFirstName,
lastName = recordLastName,
emailAddresses = recordEmailAddresses,
- enabledForCalendaring = enabledForCalendaring,
memberGUIDs = memberGUIDs,
)
- if enabledForCalendaring:
+
+ # Look up augment information
+ # TODO: this needs to be deferred but for now we hard code the deferred result because
+ # we know it is completing immediately.
+ d = augment.AugmentService.getAugmentRecord(record.guid)
+ d.addCallback(lambda x:record.addAugmentInformation(x))
+
+ if record.enabledForCalendaring:
enabledRecords.append(record)
else:
disabledRecords.append(record)
@@ -741,73 +665,6 @@
self.recordCacheForType(recordType).addRecord(record, indexType, origIndexKey)
- def _parseResourceInfo(self, plist, guid, recordType, shortname):
- """
- Parse OD ResourceInfo attribute and extract information that the server needs.
-
- @param plist: the plist that is the attribute value.
- @type plist: str
- @param guid: the directory GUID of the record being parsed.
- @type guid: str
- @param shortname: the record shortname of the record being parsed.
- @type shortname: str
- @return: a C{tuple} of C{bool} for auto-accept, C{str} for proxy GUID, C{str} for read-only proxy GUID.
- """
- try:
- plist = readPlistFromString(plist)
- wpframework = plist.get("com.apple.WhitePagesFramework", {})
- autoaccept = wpframework.get("AutoAcceptsInvitation", False)
- proxy = wpframework.get("CalendaringDelegate", None)
- read_only_proxy = wpframework.get("ReadOnlyCalendaringDelegate", None)
- except (ExpatError, AttributeError), e:
- self.log_error(
- "Failed to parse ResourceInfo attribute of record (%s)%s (guid=%s): %s\n%s" %
- (recordType, shortname, guid, e, plist,)
- )
- raise ValueError("Invalid ResourceInfo")
-
- return (autoaccept, proxy, read_only_proxy,)
-
- def getResourceInfo(self):
- """
- Resource information including proxy assignments for resource and
- locations, as well as auto-schedule settings, used to live in the
- directory. This method fetches old resource info for migration
- purposes.
- """
- attrs = [
- dsattributes.kDS1AttrGeneratedUID,
- dsattributes.kDSNAttrResourceInfo,
- ]
-
- for recordType in (dsattributes.kDSStdRecordTypePlaces, dsattributes.kDSStdRecordTypeResources):
- try:
- self.log_debug("opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r)" % (
- self.directory,
- recordType,
- attrs,
- ))
- results = opendirectory.listAllRecordsWithAttributes_list(
- self.directory,
- recordType,
- attrs,
- )
- except opendirectory.ODError, ex:
- self.log_error("OpenDirectory (node=%s) error: %s" % (self.realmName, str(ex)))
- raise
-
- for (recordShortName, value) in results:
- recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
- resourceInfo = value.get(dsattributes.kDSNAttrResourceInfo)
- if resourceInfo is not None:
- try:
- autoSchedule, proxy, readOnlyProxy = self._parseResourceInfo(resourceInfo,
- recordGUID, recordType, recordShortName)
- except ValueError:
- continue
- yield recordGUID, autoSchedule, proxy, readOnlyProxy
-
-
def isAvailable(self):
"""
Returns True if all configured directory nodes are accessible, False otherwise
@@ -860,8 +717,7 @@
"""
def __init__(
self, service, recordType, guid, nodeName, shortNames, authIDs,
- fullName, firstName, lastName, emailAddresses,
- enabledForCalendaring, memberGUIDs,
+ fullName, firstName, lastName, emailAddresses, memberGUIDs,
):
super(OpenDirectoryRecord, self).__init__(
service = service,
Copied: CalendarServer/trunk/twistedcaldav/directory/augment.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/augment.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/augment.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/augment.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,353 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin,\
+ ADBAPIPostgreSQLMixin
+from twistedcaldav.directory.xmlaugmentsparser import XMLAugmentsParser
+import copy
+import time
+
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+class AugmentRecord(object):
+ """
+ Augmented directory record information
+ """
+
+ def __init__(
+ self,
+ guid,
+ enabled=False,
+ hostedAt="",
+ enabledForCalendaring=False,
+ autoSchedule=False,
+ ):
+ self.guid = guid
+ self.enabled = enabled
+ self.hostedAt = hostedAt
+ self.enabledForCalendaring = enabledForCalendaring
+ self.autoSchedule = autoSchedule
+
+class AugmentDB(object):
+ """
+ Abstract base class for an augment record database.
+ """
+
+ def __init__(self):
+ pass
+
+ @inlineCallbacks
+ def getAugmentRecord(self, guid):
+ """
+ Get an AugmentRecord for the specified GUID or the default.
+
+ @param guid: directory GUID to lookup
+ @type guid: C{str}
+
+ @return: L{Deferred}
+ """
+
+ result = (yield self._lookupAugmentRecord(guid))
+ if result is None:
+ if not hasattr(self, "_defaultRecord"):
+ self._defaultRecord = (yield self._lookupAugmentRecord("Default"))
+ if self._defaultRecord is not None:
+ result = copy.deepcopy(self._defaultRecord)
+ result.guid = guid
+ returnValue(result)
+
+ @inlineCallbacks
+ def getAllGUIDs(self):
+ """
+ Get all AugmentRecord GUIDs.
+
+ @return: L{Deferred}
+ """
+
+ raise NotImplementedError("Child class must define this.")
+
+ def _lookupAugmentRecord(self, guid):
+ """
+ Get an AugmentRecord for the specified GUID.
+
+ @param guid: directory GUID to lookup
+ @type guid: C{str}
+
+ @return: L{Deferred}
+ """
+
+ raise NotImplementedError("Child class must define this.")
+
+ def refresh(self):
+ """
+ Refresh any cached data.
+ """
+ pass
+
+AugmentService = AugmentDB() # Global augment service
+
+
+class AugmentXMLDB(AugmentDB):
+ """
+ XMLFile based augment database implementation.
+ """
+
+ def __init__(self, xmlFiles, cacheTimeout=30):
+
+ self.xmlFiles = xmlFiles
+ self.cacheTimeout = cacheTimeout * 60 # Value is mins we want secs
+ self.lastCached = 0
+ self.db = {}
+
+ try:
+ self.db = self._parseXML()
+ except RuntimeError:
+ log.error("Failed to parse XML augments file - fatal error on startup")
+ raise
+
+ self.lastCached = time.time()
+
+ @inlineCallbacks
+ def getAllGUIDs(self):
+ """
+ Get all AugmentRecord GUIDs.
+
+ @return: L{Deferred}
+ """
+
+ return succeed(self.db.keys())
+
+ def _lookupAugmentRecord(self, guid):
+ """
+ Get an AugmentRecord for the specified GUID.
+
+ @param guid: directory GUID to lookup
+ @type guid: C{str}
+
+ @return: L{Deferred}
+ """
+
+ # May need to re-cache
+ if self.lastCached + self.cacheTimeout <= time.time():
+ self.refresh()
+
+ return succeed(self.db.get(guid))
+
+ def refresh(self):
+ """
+ Refresh any cached data.
+ """
+ try:
+ self.db = self._parseXML()
+ except RuntimeError:
+ log.error("Failed to parse XML augments file during cache refresh - ignoring")
+ self.lastCached = time.time()
+
+ def _parseXML(self):
+
+ # Do each file
+ results = {}
+ for xmlFile in self.xmlFiles:
+
+ # Creating a parser does the parse
+ XMLAugmentsParser(xmlFile, results)
+
+ return results
+
+class AugmentADAPI(AugmentDB, AbstractADBAPIDatabase):
+ """
+ DBAPI based augment database implementation.
+ """
+
+ schema_version = "1"
+ schema_type = "AugmentDB"
+
+ def __init__(self, dbID, dbapiName, dbapiArgs, **kwargs):
+
+ self.cachedPartitions = {}
+ self.cachedHostedAt = {}
+
+ AbstractADBAPIDatabase.__init__(self, dbID, dbapiName, dbapiArgs, True, **kwargs)
+
+ @inlineCallbacks
+ def getAllGUIDs(self):
+ """
+ Get all AugmentRecord GUIDs.
+
+ @return: L{Deferred}
+ """
+
+ # Query for the record information
+ results = (yield self.queryList("select GUID from AUGMENTS", ()))
+ returnValue(results)
+
+ @inlineCallbacks
+ def _lookupAugmentRecord(self, guid):
+ """
+ Get an AugmentRecord for the specified GUID.
+
+ @param guid: directory GUID to lookup
+ @type guid: C{str}
+
+ @return: L{Deferred}
+ """
+
+ # Query for the record information
+ results = (yield self.query("select GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE from AUGMENTS where GUID = :1", (guid,)))
+ if not results:
+ returnValue(None)
+ else:
+ guid, enabled, partitionid, enabdledForCalendaring, autoSchedule = results[0]
+
+ record = AugmentRecord(
+ guid = guid,
+ enabled = enabled == "T",
+ hostedAt = (yield self._getPartition(partitionid)),
+ enabledForCalendaring = enabdledForCalendaring == "T",
+ autoSchedule = autoSchedule == "T",
+ )
+
+ returnValue(record)
+
+ @inlineCallbacks
+ def addAugmentRecord(self, record, update=False):
+
+ partitionid = (yield self._getPartitionID(record.hostedAt))
+
+ if update:
+ yield self.execute(
+ """update AUGMENTS set
+ (GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE) =
+ (:1, :2, :3, :4, :5) where GUID = :6""",
+ (
+ record.guid,
+ "T" if record.enabled else "F",
+ partitionid,
+ "T" if record.enabledForCalendaring else "F",
+ "T" if record.autoSchedule else "F",
+ record.guid,
+ )
+ )
+ else:
+ yield self.execute(
+ """insert into AUGMENTS
+ (GUID, ENABLED, PARTITIONID, CALENDARING, AUTOSCHEDULE)
+ values (:1, :2, :3, :4, :5)""",
+ (
+ record.guid,
+ "T" if record.enabled else "F",
+ partitionid,
+ "T" if record.enabledForCalendaring else "F",
+ "T" if record.autoSchedule else "F",
+ )
+ )
+
+ def removeAugmentRecord(self, guid):
+
+ return self.query("delete from AUGMENTS where GUID = :1", (guid,))
+
+ @inlineCallbacks
+ def _getPartitionID(self, hostedat, createIfMissing=True):
+
+ # We will use a cache for these as we do not expect changes whilst running
+ try:
+ returnValue(self.cachedHostedAt[hostedat])
+ except KeyError:
+ pass
+
+ partitionid = (yield self.queryOne("select PARTITIONID from PARTITIONS where HOSTEDAT = :1", (hostedat,)))
+ if partitionid == None:
+ yield self.execute("insert into PARTITIONS (HOSTEDAT) values (:1)", (hostedat,))
+ partitionid = (yield self.queryOne("select PARTITIONID from PARTITIONS where HOSTEDAT = :1", (hostedat,)))
+ self.cachedHostedAt[hostedat] = partitionid
+ returnValue(partitionid)
+
+ @inlineCallbacks
+ def _getPartition(self, partitionid):
+
+ # We will use a cache for these as we do not expect changes whilst running
+ try:
+ returnValue(self.cachedPartitions[partitionid])
+ except KeyError:
+ pass
+
+ partition = (yield self.queryOne("select HOSTEDAT from PARTITIONS where PARTITIONID = :1", (partitionid,)))
+ self.cachedPartitions[partitionid] = partition
+ returnValue(partition)
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this index.
+ """
+ return AugmentADAPI.schema_version
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return AugmentADAPI.schema_type
+
+ @inlineCallbacks
+ def _db_init_data_tables(self):
+ """
+ Initialize the underlying database tables.
+ """
+
+ #
+ # TESTTYPE table
+ #
+ yield self._create_table("AUGMENTS", (
+ ("GUID", "text unique"),
+ ("ENABLED", "text(1)"),
+ ("PARTITIONID", "text"),
+ ("CALENDARING", "text(1)"),
+ ("AUTOSCHEDULE", "text(1)"),
+ ))
+
+ yield self._create_table("PARTITIONS", (
+ ("PARTITIONID", "serial"),
+ ("HOSTEDAT", "text"),
+ ))
+
+ @inlineCallbacks
+ def _db_empty_data_tables(self):
+ yield self._db_execute("delete from AUGMENTS")
+ yield self._db_execute("delete from PARTITIONS")
+
+class AugmentSqliteDB(ADBAPISqliteMixin, AugmentADAPI):
+ """
+ Sqlite based augment database implementation.
+ """
+
+ def __init__(self, dbpath):
+
+ ADBAPISqliteMixin.__init__(self)
+ AugmentADAPI.__init__(self, "Augments", "sqlite3", (dbpath,))
+
+class AugmentPostgreSQLDB(ADBAPIPostgreSQLMixin, AugmentADAPI):
+ """
+ PostgreSQL based augment database implementation.
+ """
+
+ def __init__(self, host, database, user=None, password=None):
+
+ ADBAPIPostgreSQLMixin.__init__(self)
+ AugmentADAPI.__init__(self, "Augments", "pgdb", (), host=host, database=database, user=user, password=password,)
+
Modified: CalendarServer/trunk/twistedcaldav/directory/cachingdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/cachingdirectory.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/cachingdirectory.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.directory.test.test_cachedirectory -*-
##
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -76,7 +76,7 @@
def addRecord(self, record, indexType, indexKey, useMemcache=True,
neverExpire=False):
- useMemcache = useMemcache and config.Memcached.ClientEnabled
+ useMemcache = useMemcache and config.Memcached.Pools.Default.ClientEnabled
if neverExpire:
record.neverExpire()
@@ -156,7 +156,7 @@
def _getMemcacheClient(self, refresh=False):
if refresh or not hasattr(self, "memcacheClient"):
self.memcacheClient = memcacheclient.ClientFactory.getClient(['%s:%s' %
- (config.Memcached.BindAddress, config.Memcached.Port)],
+ (config.Memcached.Pools.Default.BindAddress, config.Memcached.Pools.Default.Port)],
debug=0, pickleProtocol=2)
return self.memcacheClient
@@ -289,7 +289,7 @@
pass
# Check memcache
- if config.Memcached.ClientEnabled:
+ if config.Memcached.Pools.Default.ClientEnabled:
key = "dir|%s|%s" % (indexType, indexKey)
self.log_debug("Memcache: checking %s" % (key,))
@@ -332,7 +332,7 @@
self.log_debug("Failed to fault record for attribute '%s' with value '%s'" % (indexType, indexKey,))
self._disabledKeys[indexType][indexKey] = time.time()
- if config.Memcached.ClientEnabled:
+ if config.Memcached.Pools.Default.ClientEnabled:
self.log_debug("Memcache: storing (negative) %s" % (key,))
try:
self.memcacheSet("-%s" % (key,), 1)
@@ -349,9 +349,10 @@
class CachingDirectoryRecord(DirectoryRecord):
def __init__(
- self, service, recordType, guid, shortNames=(), authIDs=set(),
+ self, service, recordType, guid,
+ shortNames=(), authIDs=set(),
fullName=None, firstName=None, lastName=None, emailAddresses=set(),
- enabledForCalendaring=None, uid=None,
+ uid=None,
):
super(CachingDirectoryRecord, self).__init__(
service = service,
@@ -363,7 +364,6 @@
firstName = firstName,
lastName = lastName,
emailAddresses = emailAddresses,
- enabledForCalendaring = enabledForCalendaring,
uid = uid,
)
Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,7 +20,10 @@
__all__ = [
"CalendarUserProxyPrincipalResource",
- "CalendarUserProxyDatabase",
+ "ProxyDB",
+ "ProxyDBService",
+ "ProxySqliteDB",
+ "ProxyPostgreSQLDB",
]
from twisted.internet.defer import succeed, inlineCallbacks, returnValue
@@ -32,16 +35,16 @@
from twisted.web2.dav.noneprops import NonePropertyStore
from twistedcaldav.config import config
+from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin,\
+ ADBAPIPostgreSQLMixin
from twistedcaldav.extensions import DAVFile, DAVPrincipalResource
from twistedcaldav.extensions import ReadOnlyWritePropertiesResourceMixIn
from twistedcaldav.memcacher import Memcacher
from twistedcaldav.resource import CalDAVComplianceMixIn
from twistedcaldav.directory.util import NotFilePath
-from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
from twistedcaldav.log import LoggingMixIn
import itertools
-import os
import time
class PermissionsMixIn (ReadOnlyWritePropertiesResourceMixIn):
@@ -120,14 +123,10 @@
"""
Return the SQL database for this group principal.
- @return: the L{CalendarUserProxyDatabase} for the principal collection.
+ @return: the L{ProxyDB} for the principal collection.
"""
+ return ProxyDBService
- # The db is located in the principal collection root
- if not hasattr(self.pcollection, "calendar_user_proxy_db"):
- setattr(self.pcollection, "calendar_user_proxy_db", CalendarUserProxyDatabase(config.DataRoot))
- return self.pcollection.calendar_user_proxy_db
-
def resourceType(self):
if self.proxyType == "calendar-proxy-read":
return davxml.ResourceType.calendarproxyread
@@ -335,7 +334,7 @@
found.append(p)
# Make sure any outstanding deletion timer entries for
# existing principals are removed
- yield self._index()._memcacher.clearDeletionTimer(uid)
+ yield self._index().refreshPrincipal(uid)
else:
missing.append(uid)
@@ -364,7 +363,7 @@
])
return d
-class CalendarUserProxyDatabase(AbstractSQLDatabase, LoggingMixIn):
+class ProxyDB(AbstractADBAPIDatabase, LoggingMixIn):
"""
A database to maintain calendar user proxy group memberships.
@@ -376,11 +375,9 @@
"""
- dbType = "CALENDARUSERPROXY"
- dbFilename = "calendaruserproxy.sqlite"
- dbOldFilename = db_prefix + "calendaruserproxy"
- dbFormatVersion = "4"
-
+ schema_version = "4"
+ schema_type = "ProxyDB"
+
class ProxyDBMemcacher(Memcacher):
def setMembers(self, guid, members):
@@ -446,11 +443,10 @@
theTime = int(time.time())
return theTime
- def __init__(self, path):
- path = os.path.join(path, CalendarUserProxyDatabase.dbFilename)
- super(CalendarUserProxyDatabase, self).__init__(path, True)
+ def __init__(self, dbID, dbapiName, dbapiArgs, **kwargs):
+ AbstractADBAPIDatabase.__init__(self, dbID, dbapiName, dbapiArgs, True, **kwargs)
- self._memcacher = CalendarUserProxyDatabase.ProxyDBMemcacher("proxyDB")
+ self._memcacher = ProxyDB.ProxyDBMemcacher("ProxyDB")
@inlineCallbacks
def setGroupMembers(self, principalUID, members):
@@ -467,17 +463,19 @@
current_members = ()
current_members = set(current_members)
- self.setGroupMembersInDatabase(principalUID, members)
-
- # Update cache
+ # Find changes
update_members = set(members)
-
remove_members = current_members.difference(update_members)
add_members = update_members.difference(current_members)
+
+ yield self.changeGroupMembersInDatabase(principalUID, add_members, remove_members)
+
+ # Update cache
for member in itertools.chain(remove_members, add_members,):
yield self._memcacher.deleteMembership(member)
yield self._memcacher.deleteMember(principalUID)
+ @inlineCallbacks
def setGroupMembersInDatabase(self, principalUID, members):
"""
A blocking call to add a group membership record in the database.
@@ -486,11 +484,25 @@
@param members: a list UIDs of principals that are members of this group.
"""
# Remove what is there, then add it back.
- self._delete_from_db(principalUID)
- self._add_to_db(principalUID, members)
- self._db_commit()
+ yield self._delete_from_db(principalUID)
+ yield self._add_to_db(principalUID, members)
@inlineCallbacks
+ def changeGroupMembersInDatabase(self, principalUID, addMembers, removeMembers):
+ """
+ A blocking call to add a group membership record in the database.
+
+ @param principalUID: the UID of the group principal to add.
+ @param addMembers: a list UIDs of principals to be added as members of this group.
+ @param removeMembers: a list UIDs of principals to be removed as members of this group.
+ """
+ # Remove what is there, then add it back.
+ for member in removeMembers:
+ yield self._delete_from_db_one(principalUID, member)
+ for member in addMembers:
+ yield self._add_to_db_one(principalUID, member)
+
+ @inlineCallbacks
def removeGroup(self, principalUID):
"""
Remove a group membership record.
@@ -501,8 +513,7 @@
# Need to get the members before we do the delete
members = yield self.getMembers(principalUID)
- self._delete_from_db(principalUID)
- self._db_commit()
+ yield self._delete_from_db(principalUID)
# Update cache
if members:
@@ -536,7 +547,7 @@
# No timer was previously set
self.log_debug("Delaying removal of missing proxy principal '%s'"
% (principalUID,))
- self._memcacher.setDeletionTimer(principalUID, delay=delay)
+ yield self._memcacher.setDeletionTimer(principalUID, delay=delay)
returnValue(None)
self.log_warn("Removing missing proxy principal for '%s'"
@@ -544,7 +555,7 @@
for suffix in ("calendar-proxy-read", "calendar-proxy-write",):
groupUID = "%s#%s" % (principalUID, suffix,)
- self._delete_from_db(groupUID)
+ yield self._delete_from_db(groupUID)
# Update cache
members = yield self.getMembers(groupUID)
@@ -557,11 +568,20 @@
for groupUID in memberships:
yield self._memcacher.deleteMember(groupUID)
- self._delete_from_db_member(principalUID)
+ yield self._delete_from_db_member(principalUID)
yield self._memcacher.deleteMembership(principalUID)
- self._db_commit()
- self._memcacher.clearDeletionTimer(principalUID)
+ yield self._memcacher.clearDeletionTimer(principalUID)
+ def refreshPrincipal(self, principalUID):
+ """
+ Bring back to life a principal that was previously deleted.
+
+ @param principalUID:
+ @type principalUID:
+ """
+
+ return self._memcacher.clearDeletionTimer(principalUID)
+
def getMembers(self, principalUID):
"""
Return the list of group member UIDs for the specified principal.
@@ -573,12 +593,14 @@
return members
# Cache miss; compute members and update cache
- members = set([
- row[0] for row in
- self._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", principalUID)
- ])
- d = self._memcacher.setMembers(principalUID, members)
- d.addCallback(lambda _: members)
+ def gotMembersFromDB(dbmembers):
+ members = set([row[0] for row in dbmembers])
+ d = self._memcacher.setMembers(principalUID, members)
+ d.addCallback(lambda _: members)
+ return d
+
+ d = self.query("select MEMBER from GROUPS where GROUPNAME = :1", (principalUID,))
+ d.addCallback(gotMembersFromDB)
return d
d = self._memcacher.getMembers(principalUID)
@@ -596,18 +618,21 @@
return memberships
# Cache miss; compute memberships and update cache
- memberships = set([
- row[0] for row in
- self._db_execute("select GROUPNAME from GROUPS where MEMBER = :1", principalUID)
- ])
- d = self._memcacher.setMemberships(principalUID, memberships)
- d.addCallback(lambda _: memberships)
+ def gotMembershipsFromDB(dbmemberships):
+ memberships = set([row[0] for row in dbmemberships])
+ d = self._memcacher.setMemberships(principalUID, memberships)
+ d.addCallback(lambda _: memberships)
+ return d
+
+ d = self.query("select GROUPNAME from GROUPS where MEMBER = :1", (principalUID,))
+ d.addCallback(gotMembershipsFromDB)
return d
d = self._memcacher.getMemberships(principalUID)
d.addCallback(gotCachedMemberships)
return d
+ @inlineCallbacks
def _add_to_db(self, principalUID, members):
"""
Insert the specified entry into the database.
@@ -616,42 +641,66 @@
@param members: a list of UIDs or principals that are members of this group.
"""
for member in members:
- self._db_execute(
+ yield self.execute(
"""
insert into GROUPS (GROUPNAME, MEMBER)
values (:1, :2)
- """, principalUID, member
+ """, (principalUID, member,)
)
+ def _add_to_db_one(self, principalUID, memberUID):
+ """
+ Insert the specified entry into the database.
+
+ @param principalUID: the UID of the group principal to add.
+ @param memberUID: the UID of the principal that is being added as a member of this group.
+ """
+ return self.execute(
+ """
+ insert into GROUPS (GROUPNAME, MEMBER)
+ values (:1, :2)
+ """, (principalUID, memberUID,)
+ )
+
def _delete_from_db(self, principalUID):
"""
Deletes the specified entry from the database.
@param principalUID: the UID of the group principal to remove.
"""
- self._db_execute("delete from GROUPS where GROUPNAME = :1", principalUID)
+ return self.execute("delete from GROUPS where GROUPNAME = :1", (principalUID,))
+ def _delete_from_db_one(self, principalUID, memberUID):
+ """
+ Deletes the specified entry from the database.
+
+ @param principalUID: the UID of the group principal to remove.
+ @param memberUID: the UID of the principal that is being removed as a member of this group.
+ """
+ return self.execute("delete from GROUPS where GROUPNAME = :1 and MEMBER = :2", (principalUID, memberUID,))
+
def _delete_from_db_member(self, principalUID):
"""
Deletes the specified member entry from the database.
@param principalUID: the UID of the member principal to remove.
"""
- self._db_execute("delete from GROUPS where MEMBER = :1", principalUID)
+ return self.execute("delete from GROUPS where MEMBER = :1", (principalUID,))
def _db_version(self):
"""
@return: the schema version assigned to this index.
"""
- return CalendarUserProxyDatabase.dbFormatVersion
+ return ProxyDB.schema_version
def _db_type(self):
"""
@return: the collection type assigned to this index.
"""
- return CalendarUserProxyDatabase.dbType
+ return ProxyDB.schema_type
- def _db_init_data_tables(self, q):
+ @inlineCallbacks
+ def _db_init_data_tables(self):
"""
Initialise the underlying database tables.
@param q: a database cursor to use.
@@ -660,48 +709,90 @@
#
# GROUPS table
#
- q.execute(
+ yield self._create_table("GROUPS", (
+ ("GROUPNAME", "text"),
+ ("MEMBER", "text"),
+ ))
+
+ yield self._db_execute(
"""
- create table GROUPS (
- GROUPNAME text,
- MEMBER text
- )
- """
- )
- q.execute(
- """
create index GROUPNAMES on GROUPS (GROUPNAME)
"""
)
- q.execute(
+ yield self._db_execute(
"""
create index MEMBERS on GROUPS (MEMBER)
"""
)
- def _db_upgrade_data_tables(self, q, old_version):
+ @inlineCallbacks
+ def _db_upgrade_data_tables(self, old_version):
"""
Upgrade the data from an older version of the DB.
- @param q: a database cursor to use.
@param old_version: existing DB's version number
@type old_version: str
"""
# Add index if old version is less than "4"
if int(old_version) < 4:
- q.execute(
+ yield self._db_execute(
"""
create index GROUPNAMES on GROUPS (GROUPNAME)
"""
)
- q.execute(
+ yield self._db_execute(
"""
create index MEMBERS on GROUPS (MEMBER)
"""
)
+ def _db_empty_data_tables(self):
+ """
+ Empty the underlying database tables.
+ @param q: a database cursor to use.
+ """
+ #
+ # GROUPS table
+ #
+ return self._db_execute("delete from GROUPS")
+ @inlineCallbacks
+ def clean(self):
+
+ if not self.initialized:
+ yield self.open()
+
+ for group in [row[0] for row in (yield self.query("select GROUPNAME from GROUPS"))]:
+ self.removeGroup(group)
+
+ yield super(ProxyDB, self).clean()
+
+
+ProxyDBService = None # Global proxyDB service
+
+
+class ProxySqliteDB(ADBAPISqliteMixin, ProxyDB):
+ """
+ Sqlite based proxy database implementation.
+ """
+
+ def __init__(self, dbpath):
+
+ ADBAPISqliteMixin.__init__(self)
+ ProxyDB.__init__(self, "Proxies", "sqlite3", (dbpath,))
+
+class ProxyPostgreSQLDB(ADBAPIPostgreSQLMixin, ProxyDB):
+ """
+ PostgreSQL based augment database implementation.
+ """
+
+ def __init__(self, host, database, user=None, password=None):
+
+ ADBAPIPostgreSQLMixin.__init__(self, )
+ ProxyDB.__init__(self, "Proxies", "pgdb", (), host=host, database=database, user=user, password=password,)
+
+
##
# Utilities
##
Copied: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxyloader.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/calendaruserproxyloader.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxyloader.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxyloader.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,137 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from xml.etree.ElementTree import ElementTree
+from xml.parsers.expat import ExpatError
+from twistedcaldav.directory import calendaruserproxy
+from twisted.internet.defer import inlineCallbacks
+import types
+
+"""
+XML based calendar user proxy loader.
+"""
+
+__all__ = [
+ "XMLCalendarUserProxyLoader",
+]
+
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+ELEMENT_PROXIES = "proxies"
+ELEMENT_RECORD = "record"
+
+ELEMENT_GUID = "guid"
+ELEMENT_PROXIES = "proxies"
+ELEMENT_READ_ONLY_PROXIES = "read-only-proxies"
+ELEMENT_MEMBER = "member"
+
+ATTRIBUTE_REPEAT = "repeat"
+
+class XMLCalendarUserProxyLoader(object):
+ """
+ XML calendar user proxy configuration file parser and loader.
+ """
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+ def __init__(self, xmlFile):
+
+ self.items = []
+ self.xmlFile = xmlFile
+
+ # Read in XML
+ try:
+ tree = ElementTree(file=self.xmlFile)
+ except ExpatError, e:
+ log.error("Unable to parse file '%s' because: %s" % (self.xmlFile, e,), raiseException=RuntimeError)
+
+ # Verify that top-level element is correct
+ proxies_node = tree.getroot()
+ if proxies_node.tag != ELEMENT_PROXIES:
+ log.error("Ignoring file '%s' because it is not a proxies file" % (self.xmlFile,), raiseException=RuntimeError)
+
+ self._parseXML(proxies_node)
+
+ def _parseXML(self, rootnode):
+ """
+ Parse the XML root node from the augments configuration document.
+ @param rootnode: the L{Element} to parse.
+ """
+ for child in rootnode.getchildren():
+
+ if child.tag != ELEMENT_RECORD:
+ log.error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, self.xmlFile,), raiseException=RuntimeError)
+
+ repeat = int(child.get(ATTRIBUTE_REPEAT, "1"))
+
+ guid = None
+ write_proxies = set()
+ read_proxies = set()
+ for node in child.getchildren():
+
+ if node.tag == ELEMENT_GUID:
+ guid = node.text
+
+ elif node.tag in (
+ ELEMENT_PROXIES,
+ ELEMENT_READ_ONLY_PROXIES,
+ ):
+ self._parseMembers(node, write_proxies if node.tag == ELEMENT_PROXIES else read_proxies)
+ else:
+ log.error("Invalid element '%s' in proxies file: '%s'" % (node.tag, self.xmlFile,), raiseException=RuntimeError)
+
+ # Must have at least a guid
+ if not guid:
+ log.error("Invalid record '%s' without a guid in proxies file: '%s'" % (child, self.xmlFile,), raiseException=RuntimeError)
+
+ if repeat > 1:
+ for i in xrange(1, repeat+1):
+ self._buildRecord(guid, write_proxies, read_proxies, i)
+ else:
+ self._buildRecord(guid, write_proxies, read_proxies)
+
+ def _parseMembers(self, node, addto):
+ for child in node.getchildren():
+ if child.tag == ELEMENT_MEMBER:
+ addto.add(child.text)
+
+ def _buildRecord(self, guid, write_proxies, read_proxies, count=None):
+
+ def expandCount(value, count):
+
+ if type(value) in types.StringTypes:
+ return value % (count,) if count and "%" in value else value
+ else:
+ return value
+
+ guid = expandCount(guid, count)
+ write_proxies = set([expandCount(member, count) for member in write_proxies])
+ read_proxies = set([expandCount(member, count) for member in read_proxies])
+
+ self.items.append((guid, write_proxies, read_proxies,))
+
+ @inlineCallbacks
+ def updateProxyDB(self):
+
+ db = calendaruserproxy.ProxyDBService
+ for item in self.items:
+ guid, write_proxies, read_proxies = item
+ for proxy in write_proxies:
+ yield db.setGroupMembers("%s#%s" % (guid, "calendar-proxy-write"), (proxy,))
+ for proxy in read_proxies:
+ yield db.setGroupMembers("%s#%s" % (guid, "calendar-proxy-read"), (proxy,))
Modified: CalendarServer/trunk/twistedcaldav/directory/digest.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/digest.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/digest.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -163,7 +163,7 @@
raise AssertionError("nonce value already cached in credentials database: %s" % (c,))
# The database record is a tuple of (client ip, nonce-count, timestamp)
- yield self.db.set(c, (peer.host, 0, time.time()))
+ yield self.db.set(c, (str(peer.host), 0, time.time()))
challenge = {
'nonce': c,
@@ -262,7 +262,7 @@
"""
nonce = auth.get('nonce')
- clientip = request.remoteAddr.host
+ clientip = request.forwarded_for if hasattr(request, "forwarded_for") else str(request.remoteAddr.host)
nonce_count = auth.get('nc')
# First check we have this nonce
Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.directory.test -*-
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
# limitations under the License.
##
+
"""
Generic directory service classes.
"""
@@ -37,9 +38,11 @@
from twisted.web2.dav.auth import IPrincipalCredentials
from twisted.internet.defer import succeed
+from twistedcaldav.config import config
from twistedcaldav.log import LoggingMixIn
from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
from twistedcaldav.directory.util import uuidFromName
+from twistedcaldav.partitions import partitions
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
class DirectoryService(LoggingMixIn):
@@ -150,7 +153,7 @@
record = self.recordWithGUID(guid)
elif address.startswith("mailto:"):
for record in self.allRecords():
- if address in record.calendarUserAddresses:
+ if address[7:] in record.emailAddresses:
break
else:
return None
@@ -282,20 +285,21 @@
implements(IDirectoryRecord)
def __repr__(self):
- return "<%s[%s@%s(%s)] %s(%s) %r>" % (
+ return "<%s[%s@%s(%s)] %s(%s) %r @ %s>" % (
self.__class__.__name__,
self.recordType,
self.service.guid,
self.service.realmName,
self.guid,
",".join(self.shortNames),
- self.fullName
+ self.fullName,
+ self.hostedAt,
)
def __init__(
- self, service, recordType, guid, shortNames=(), authIDs=set(), fullName=None,
+ self, service, recordType, guid,
+ shortNames=(), authIDs=set(), fullName=None,
firstName=None, lastName=None, emailAddresses=set(),
- enabledForCalendaring=None,
uid=None,
):
assert service.realmName is not None
@@ -311,28 +315,21 @@
if fullName is None:
fullName = ""
- if enabledForCalendaring is None:
- if recordType == service.recordType_groups:
- enabledForCalendaring = False
- else:
- enabledForCalendaring = True
-
- if enabledForCalendaring and recordType == service.recordType_groups:
- raise AssertionError("Groups may not be enabled for calendaring")
-
self.service = service
self.recordType = recordType
self.guid = guid
self.uid = uid
+ self.enabled = False
+ self.hostedAt = ""
self.shortNames = shortNames
self.authIDs = authIDs
self.fullName = fullName
self.firstName = firstName
self.lastName = lastName
self.emailAddresses = emailAddresses
- self.enabledForCalendaring = enabledForCalendaring
+ self.enabledForCalendaring = False
+ self.autoSchedule = False
-
def get_calendarUserAddresses(self):
"""
Dynamically construct a calendarUserAddresses attribute which describes
@@ -350,7 +347,6 @@
calendarUserAddresses = property(get_calendarUserAddresses)
-
def __cmp__(self, other):
if not isinstance(other, DirectoryRecord):
return NotImplemented
@@ -364,11 +360,28 @@
def __hash__(self):
h = hash(self.__class__)
for attr in ("service", "recordType", "shortNames", "guid",
- "enabledForCalendaring"):
+ "enabled", "enabledForCalendaring"):
h = (h + hash(getattr(self, attr))) & sys.maxint
return h
+ def addAugmentInformation(self, augment):
+
+ if augment:
+ self.enabled = augment.enabled
+ self.hostedAt = augment.hostedAt
+ self.enabledForCalendaring = augment.enabledForCalendaring
+ self.autoSchedule = augment.autoSchedule
+
+ if self.enabledForCalendaring and self.recordType == self.service.recordType_groups:
+ self.log_error("Group '%s(%s)' cannot be enabled for calendaring" % (self.guid, self.shortNames[0],))
+ self.enabledForCalendaring = False
+
+ else:
+ self.enabled = False
+ self.hostedAt = ""
+ self.enabledForCalendaring = False
+
def members(self):
return ()
@@ -396,6 +409,12 @@
return key
return None
+ def locallyHosted(self):
+ return not self.hostedAt or not config.Partitioning.Enabled or self.hostedAt == config.Partitioning.ServerPartitionID
+
+ def hostedURL(self):
+ return partitions.getPartitionURL(self.hostedAt)
+
class DirectoryError(RuntimeError):
"""
Generic directory error.
Modified: CalendarServer/trunk/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/idirectory.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/idirectory.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -96,12 +96,15 @@
recordType = Attribute("The type of this record.")
guid = Attribute("The GUID of this record.")
uid = Attribute("The UID of this record.")
+ enabled = Attribute("Determines whether this record should be provisioned as a principal.")
+ hostedAt = Attribute("Identifies the server that actually hosts data for the record.")
shortNames = Attribute("The names for this record.")
authIDs = Attribute("Alternative security identities for this record.")
fullName = Attribute("The full name of this record.")
firstName = Attribute("The first name of this record.")
lastName = Attribute("The last name of this record.")
emailAddress = Attribute("The email address of this record.")
+ enabledForCalendaring = Attribute("Determines whether this record should be provisioned with a calendar home.")
calendarUserAddresses = Attribute(
"""
An iterable of C{str}s representing calendar user addresses for this
@@ -114,7 +117,6 @@
hierarchy.
"""
)
- enabledForCalendaring = Attribute("Determines whether this record should be provisioned with a calendar home.")
def members():
"""
Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.directory.test.test_principal -*-
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -52,8 +52,7 @@
from twistedcaldav.config import config
from twistedcaldav.cache import DisabledCacheNotifier, PropfindCacheMixin
-from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
-from twistedcaldav.directory.resourceinfo import ResourceInfoDatabase
+from twistedcaldav.directory import calendaruserproxy
from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyPrincipalResource
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
from twistedcaldav.directory.util import NotFilePath
@@ -190,7 +189,7 @@
raise NotImplementedError("Subclass must implement principalForCalendarUserAddress()")
def principalForRecord(self, record):
- if record is None:
+ if record is None or not record.enabled:
return None
return self.principalForUID(record.uid)
@@ -329,7 +328,7 @@
else:
# Next try looking it up in the directory
record = self.directory.recordWithCalendarUserAddress(address)
- if record is not None:
+ if record is not None and record.enabled:
return self.principalForRecord(record)
log.debug("No principal for calendar user address: %r" % (address,))
@@ -410,8 +409,9 @@
def _recordShortnameExpand():
for record in self.directory.listRecords(self.recordType):
- for shortName in record.shortNames:
- yield shortName
+ if record.enabled:
+ for shortName in record.shortNames:
+ yield shortName
return _recordShortnameExpand()
else:
@@ -485,8 +485,7 @@
record = self.directory.recordWithUID(primaryUID)
- primaryPrincipal = self.principalForRecord(record)
-
+ primaryPrincipal = self.principalForRecord(record) if record and record.enabled else None
if primaryPrincipal is None:
log.err("No principal found for UID: %s" % (name,))
return None
@@ -527,7 +526,7 @@
"""
super(DirectoryPrincipalResource, self).__init__(NotFilePath(isdir=True))
- self.cacheNotifier = self.cacheNotifierFactory(self)
+ self.cacheNotifier = self.cacheNotifierFactory(self, cacheHandle="PrincipalToken")
if self.isCollection():
slash = "/"
@@ -660,16 +659,11 @@
"""
Return the SQL database for calendar user proxies.
- @return: the L{CalendarUserProxyDatabase} for the principal collection.
+ @return: the L{ProxyDB} for the principal collection.
"""
- # Get the principal collection we are contained in
- pcollection = self.parent.parent
-
# The db is located in the principal collection root
- if not hasattr(pcollection, "calendar_user_proxy_db"):
- setattr(pcollection, "calendar_user_proxy_db", CalendarUserProxyDatabase(config.DataRoot))
- return pcollection.calendar_user_proxy_db
+ return calendaruserproxy.ProxyDBService
def alternateURIs(self):
# FIXME: Add API to IDirectoryRecord for getting a record URI?
@@ -777,45 +771,22 @@
def principalUID(self):
return self.record.uid
+ def locallyHosted(self):
+ return self.record.locallyHosted()
+
+ def hostedURL(self):
+ return self.record.hostedURL()
##
# Extra resource info
##
def setAutoSchedule(self, autoSchedule):
- self._resource_info_index().setAutoSchedule(self.record.guid, autoSchedule)
+ self.record.autoSchedule = autoSchedule
- @inlineCallbacks
def getAutoSchedule(self):
- value = (yield self._resource_info_index().getAutoSchedule(self.record.guid))
- if value is None:
- # No value has been explicitly set yet. If this is a user/group
- # the default should be False. If resource/location, True.
- if self.record.recordType in (DirectoryService.recordType_locations,
- DirectoryService.recordType_resources):
- yield self.setAutoSchedule(True)
- returnValue(True)
- else:
- yield self.setAutoSchedule(False)
- returnValue(False)
- else:
- returnValue(value)
+ return self.record.autoSchedule
-
- def _resource_info_index(self):
- """
- Return the resource info SQL database for this calendar principal.
-
- @return: the L{ResourceInfoDatabase} for the calendar principal.
- """
-
- # The db is located in the data root
- self.pcollection = self.parent.parent
- if not hasattr(self.pcollection, "resource_info_db"):
- setattr(self.pcollection, "resource_info_db", ResourceInfoDatabase(config.DataRoot))
- return self.pcollection.resource_info_db
-
-
##
# Static
##
Deleted: CalendarServer/trunk/twistedcaldav/directory/sqldb.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/sqldb.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/sqldb.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,368 +0,0 @@
-##
-# Copyright (c) 2006-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.
-##
-
-"""
-SQL (sqlite) based user/group/resource directory service implementation.
-"""
-
-"""
-SCHEMA:
-
-User Database:
-
-ROW: RECORD_TYPE, SHORT_NAME (unique), PASSWORD, NAME
-
-Group Database:
-
-ROW: SHORT_NAME, MEMBER_SHORT_NAME
-
-CUAddress database:
-
-ROW: ADDRESS (unqiue), SHORT_NAME
-
-"""
-
-__all__ = [
- "SQLDirectoryService",
-]
-
-from twisted.cred.credentials import UsernamePassword
-from twisted.python.filepath import FilePath
-
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.sql import db_prefix
-
-import os
-
-class SQLDirectoryManager(AbstractSQLDatabase):
- """
- House keeping operations on the SQL DB, including loading from XML file,
- and record dumping. This can be used as a standalong DB management tool.
- """
- dbType = "DIRECTORYSERVICE"
- dbFilename = db_prefix + "accounts"
- dbFormatVersion = "3"
-
- def __init__(self, path):
- path = os.path.join(path, SQLDirectoryManager.dbFilename)
- super(SQLDirectoryManager, self).__init__(path, True)
-
- def loadFromXML(self, xmlFile):
- parser = XMLAccountsParser(xmlFile)
-
- # Totally wipe existing DB and start from scratch
- if os.path.exists(self.dbpath):
- os.remove(self.dbpath)
-
- self._db_execute("insert into SERVICE (REALM) values (:1)", parser.realm)
-
- # Now add records to db
- for item in parser.items.values():
- for entry in item.itervalues():
- self._add_to_db(entry)
- self._db_commit()
-
- def getRealm(self):
- for realm in self._db_execute("select REALM from SERVICE"):
- return realm[0].decode("utf-8")
- else:
- return ""
-
- def listRecords(self, recordType):
- # Get each account record
- for shortName, guid, password, name in self._db_execute(
- """
- select SHORT_NAME, GUID, PASSWORD, NAME
- from ACCOUNTS
- where RECORD_TYPE = :1
- """, recordType
- ):
- # See if we have members
- members = self.members(shortName)
-
- # See if we are a member of any groups
- groups = self.groups(shortName)
-
- # Get calendar user addresses
- calendarUserAddresses = self.calendarUserAddresses(shortName)
-
- yield shortName, guid, password, name, members, groups, calendarUserAddresses
-
- def getRecord(self, recordType, shortName):
- # Get individual account record
- for shortName, guid, password, name in self._db_execute(
- """
- select SHORT_NAME, GUID, PASSWORD, NAME
- from ACCOUNTS
- where RECORD_TYPE = :1
- and SHORT_NAME = :2
- """, recordType, shortName
- ):
- break
- else:
- return None
-
- # See if we have members
- members = self.members(shortName)
-
- # See if we are a member of any groups
- groups = self.groups(shortName)
-
- # Get calendar user addresses
- calendarUserAddresses = self.calendarUserAddresses(shortName)
-
- return shortName, guid, password, name, members, groups, calendarUserAddresses
-
- def members(self, shortName):
- members = set()
- for member in self._db_execute(
- """
- select MEMBER_RECORD_TYPE, MEMBER_SHORT_NAME
- from GROUPS
- where SHORT_NAME = :1
- """, shortName
- ):
- members.add(tuple(member))
- return members
-
- def groups(self, shortName):
- groups = set()
- for (name,) in self._db_execute(
- """
- select SHORT_NAME
- from GROUPS
- where MEMBER_SHORT_NAME = :1
- """, shortName
- ):
- groups.add(name)
- return groups
-
- def calendarUserAddresses(self, shortName):
- calendarUserAddresses = set()
- for (address,) in self._db_execute(
- """
- select ADDRESS
- from ADDRESSES
- where SHORT_NAME = :1
- """, shortName
- ):
- calendarUserAddresses.add(address)
- return calendarUserAddresses
-
- def _add_to_db(self, record):
- # Do regular account entry
- recordType = record.recordType
- shortName = record.shortNames[0]
- guid = record.guid
- password = record.password
- name = record.fullName
-
- self._db_execute(
- """
- insert into ACCOUNTS (RECORD_TYPE, SHORT_NAME, GUID, PASSWORD, NAME)
- values (:1, :2, :3, :4, :5)
- """, recordType, shortName, guid, password, name
- )
-
- # Check for members
- for memberRecordType, memberShortName in record.members:
- self._db_execute(
- """
- insert into GROUPS (SHORT_NAME, MEMBER_RECORD_TYPE, MEMBER_SHORT_NAME)
- values (:1, :2, :3)
- """, shortName, memberRecordType, memberShortName
- )
-
- # CUAddress
- for cuaddr in record.calendarUserAddresses:
- self._db_execute(
- """
- insert into ADDRESSES (ADDRESS, SHORT_NAME)
- values (:1, :2)
- """, cuaddr, shortName
- )
-
- def _delete_from_db(self, shortName):
- """
- Deletes the specified entry from all dbs.
- @param name: the name of the resource to delete.
- @param shortName: the short name of the resource to delete.
- """
- self._db_execute("delete from ACCOUNTS where SHORT_NAME = :1", shortName)
- self._db_execute("delete from GROUPS where SHORT_NAME = :1", shortName)
- self._db_execute("delete from GROUPS where MEMBER_SHORT_NAME = :1", shortName)
- self._db_execute("delete from ADDRESSES where SHORT_NAME = :1", shortName)
-
- def _db_version(self):
- """
- @return: the schema version assigned to this index.
- """
- return SQLDirectoryManager.dbFormatVersion
-
- def _db_type(self):
- """
- @return: the collection type assigned to this index.
- """
- return SQLDirectoryManager.dbType
-
- def _db_init_data_tables(self, q):
- """
- Initialise the underlying database tables.
- @param q: a database cursor to use.
- """
- #
- # SERVICE table
- #
- q.execute("create table SERVICE (REALM text)")
-
- #
- # ACCOUNTS table
- #
- q.execute(
- """
- create table ACCOUNTS (
- RECORD_TYPE text,
- SHORT_NAME text,
- GUID text,
- PASSWORD text,
- NAME text
- )
- """
- )
-
- #
- # GROUPS table
- #
- q.execute(
- """
- create table GROUPS (
- SHORT_NAME text,
- MEMBER_RECORD_TYPE text,
- MEMBER_SHORT_NAME text
- )
- """
- )
-
- #
- # ADDRESSES table
- #
- q.execute(
- """
- create table ADDRESSES (
- ADDRESS text unique,
- SHORT_NAME text
- )
- """
- )
-
-class SQLDirectoryService(DirectoryService):
- """
- XML based implementation of L{IDirectoryService}.
- """
- baseGUID = "8256E464-35E0-4DBB-A99C-F0E30C231675"
- realmName = None
-
- def __repr__(self):
- return "<%s %r: %r>" % (self.__class__.__name__, self.realmName, self.manager.dbpath)
-
- def __init__(self, dbParentPath, xmlFile=None):
- super(SQLDirectoryService, self).__init__()
-
- if type(dbParentPath) is str:
- dbParentPath = FilePath(dbParentPath)
-
- self.manager = SQLDirectoryManager(dbParentPath.path)
- if xmlFile:
- self.manager.loadFromXML(xmlFile)
- self.realmName = self.manager.getRealm()
-
- def recordTypes(self):
- recordTypes = (
- DirectoryService.recordType_users,
- DirectoryService.recordType_groups,
- DirectoryService.recordType_locations,
- DirectoryService.recordType_resources,
- )
- return recordTypes
-
- def listRecords(self, recordType):
- for result in self.manager.listRecords(recordType):
- yield SQLDirectoryRecord(
- service = self,
- recordType = recordType,
- shortName = result[0],
- guid = result[1],
- password = result[2],
- name = result[3],
- members = result[4],
- groups = result[5],
- calendarUserAddresses = result[6],
- )
-
- def recordWithShortName(self, recordType, shortName):
- result = self.manager.getRecord(recordType, shortName)
- if result:
- return SQLDirectoryRecord(
- service = self,
- recordType = recordType,
- shortName = result[0],
- guid = result[1],
- password = result[2],
- name = result[3],
- members = result[4],
- groups = result[5],
- calendarUserAddresses = result[6],
- )
-
- return None
-
-class SQLDirectoryRecord(DirectoryRecord):
- """
- XML based implementation implementation of L{IDirectoryRecord}.
- """
- def __init__(self, service, recordType, shortName, guid, password, name, members, groups, calendarUserAddresses):
- super(SQLDirectoryRecord, self).__init__(
- service = service,
- recordType = recordType,
- guid = guid,
- shortNames = (shortName,),
- fullName = name,
- calendarUserAddresses = calendarUserAddresses,
- )
-
- self.password = password
- self._members = members
- self._groups = groups
-
- def members(self):
- for recordType, shortName in self._members:
- yield self.service.recordWithShortName(recordType, shortName)
-
- def groups(self):
- for shortName in self._groups:
- yield self.service.recordWithShortName(DirectoryService.recordType_groups, shortName)
-
- def verifyCredentials(self, credentials):
- if isinstance(credentials, UsernamePassword):
- return credentials.password == self.password
-
- return super(SQLDirectoryRecord, self).verifyCredentials(credentials)
-
-if __name__ == '__main__':
- mgr = SQLDirectoryManager("./")
- mgr.loadFromXML("test/accounts.xml")
Modified: CalendarServer/trunk/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/sudo.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/sudo.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -127,11 +127,12 @@
guid=None,
shortNames=(shortName,),
fullName=shortName,
- enabledForCalendaring=False,
)
self.password = entry['password']
+ self.enabled = True # Explicitly enabled
+
def verifyCredentials(self, credentials):
if IUsernamePassword.providedBy(credentials):
return credentials.checkPassword(self.password)
Modified: CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+Copyright (c) 2006-2010 Apple Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
limitations under the License.
-->
-<!DOCTYPE accounts SYSTEM "../../../conf/accounts.dtd">
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
<accounts realm="Test">
<user>
@@ -66,7 +66,6 @@
<password>radnelacon</password>
<name>No Calendar</name>
<email-address>nocalendar at example.com</email-address>
- <disable-calendar/>
</user>
<user repeat="2">
<uid>user%02d</uid>
@@ -162,7 +161,6 @@
<member>cdaboo</member>
<member>lecroy</member>
</members>
- <disable-calendar/>
</group>
<location>
<uid>mercury</uid>
@@ -170,9 +168,6 @@
<password>mercury</password>
<name>Mecury Seven</name>
<email-address>mercury at example.com</email-address>
- <proxies>
- <member type="groups">left_coast</member>
- </proxies>
</location>
<location>
<uid>gemini</uid>
@@ -180,10 +175,6 @@
<password>gemini</password>
<name>Gemini Twelve</name>
<email-address>gemini at example.com</email-address>
- <auto-schedule/>
- <proxies>
- <member>wsanchez</member>
- </proxies>
</location>
<location>
<uid>apollo</uid>
@@ -191,9 +182,6 @@
<password>apollo</password>
<name>Apollo Eleven</name>
<email-address>apollo at example.com</email-address>
- <proxies>
- <member type="groups">both_coasts</member>
- </proxies>
</location>
<location>
<uid>orion</uid>
@@ -201,9 +189,6 @@
<password>orion</password>
<name>Orion</name>
<email-address>orion at example.com</email-address>
- <proxies>
- <member type="groups">recursive1_coasts</member>
- </proxies>
</location>
<resource>
<uid>transporter</uid>
@@ -225,11 +210,5 @@
<password>non_calendar_proxy</password>
<name>Non-calendar proxy</name>
<email-address>non_calendar_proxy at example.com</email-address>
- <proxies>
- <member type="groups">non_calendar_group</member>
- </proxies>
- <read-only-proxies>
- <member type="groups">recursive2_coasts</member>
- </read-only-proxies>
</resource>
</accounts>
Copied: CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/test/augments-test-default.xml)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments-test-default.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/augments.dtd">
+
+<augments>
+ <record>
+ <guid>Default</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <hosted-at>00001</hosted-at>
+ </record>
+ <record>
+ <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+ <enable>false</enable>
+ </record>
+ <record>
+ <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+ <enable>true</enable>
+ <enable-calendar>false</enable-calendar>
+ </record>
+ <record>
+ <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+ <enable>true</enable>
+ <hosted-at>00001</hosted-at>
+ </record>
+ <record>
+ <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+ <enable>true</enable>
+ <hosted-at>00002</hosted-at>
+ </record>
+ <record>
+ <guid>6A73326A-F781-47E7-A9F8-AF47364D4152</guid>
+ <enable>true</enable>
+ <hosted-at>00002</hosted-at>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <auto-schedule>true</auto-schedule>
+ </record>
+</augments>
Copied: CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/test/augments-test.xml)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments-test.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/augments.dtd">
+
+<augments>
+ <record>
+ <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+ <enable>false</enable>
+ </record>
+ <record>
+ <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+ <enable>true</enable>
+ <enable-calendar>false</enable-calendar>
+ </record>
+ <record>
+ <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+ <enable>true</enable>
+ <hosted-at>00001</hosted-at>
+ </record>
+ <record>
+ <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+ <enable>true</enable>
+ <hosted-at>00002</hosted-at>
+ </record>
+ <record>
+ <guid>6A73326A-F781-47E7-A9F8-AF47364D4152</guid>
+ <enable>true</enable>
+ <hosted-at>00002</hosted-at>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <auto-schedule>true</auto-schedule>
+ </record>
+</augments>
Copied: CalendarServer/trunk/twistedcaldav/directory/test/augments.xml (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/test/augments.xml)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments.xml (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE augments SYSTEM "../../../conf/auth/augments.dtd">
+
+<augments realm="Test">
+ <record>
+ <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
+ <enable>true</enable>
+ <enable-calendar>false</enable-calendar>
+ </record>
+ <record repeat="2">
+ <guid>user%02d</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>admin</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>grunts</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>right_coast</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>left_coast</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>both_coasts</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>recursive1_coasts</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>recursive2_coasts</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>non_calendar_group</guid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <guid>mercury</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>gemini</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>apollo</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>orion</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>transporter</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>ftlcpu</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+ <record>
+ <guid>non_calendar_proxy</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ </record>
+</augments>
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/basic
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/basic 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/basic 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,4 +0,0 @@
-wsanchez:Cytm0Bwm7CPJs
-cdaboo:I.Ef5FJl5GVh2
-dreid:LVhqAv4qSrYPs
-lecroy:/7/5VDrkrLxY.
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/digest
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/digest 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/digest 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,4 +0,0 @@
-wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
-cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
-dreid:Test:8ee67801004b2752f72b84e7064889a6
-lecroy:Test:60d4feb424430953be045738041e51be
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/groups
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/groups 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/groups 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,4 +0,0 @@
-managers: lecroy
-grunts: wsanchez, cdaboo, dreid
-right_coast: cdaboo
-left_coast: wsanchez, dreid, lecroy
Copied: CalendarServer/trunk/twistedcaldav/directory/test/proxies.xml (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/test/proxies.xml)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/proxies.xml (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/proxies.xml 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE proxies SYSTEM "proxies.dtd">
+
+<proxies>
+ <record>
+ <guid>mercury</guid>
+ <proxies>
+ <member>left_coast</member>
+ </proxies>
+ </record>
+ <record>
+ <guid>gemini</guid>
+ <proxies>
+ <member>6423F94A-6B76-4A3A-815B-D52CFD77935D</member>
+ </proxies>
+ </record>
+ <record>
+ <guid>apollo</guid>
+ <proxies>
+ <member>both_coasts</member>
+ </proxies>
+ </record>
+ <record>
+ <guid>orion</guid>
+ <proxies>
+ <member>recursive1_coasts</member>
+ </proxies>
+ </record>
+ <record>
+ <guid>non_calendar_proxy</guid>
+ <proxies>
+ <member>non_calendar_group</member>
+ </proxies>
+ <read-only-proxies>
+ <member>recursive2_coasts</member>
+ </read-only-proxies>
+ </record>
+</proxies>
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_aggregate.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_aggregate.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_aggregate.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,20 +14,18 @@
# limitations under the License.
##
-from twistedcaldav.directory.apache import BasicDirectoryService
from twistedcaldav.directory.xmlfile import XMLDirectoryService
from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.test.test_apache import digestRealm, basicUserFile, groupFile
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
import twistedcaldav.directory.test.util
+from twistedcaldav.directory import augment
apache_prefix = "apache:"
xml_prefix = "xml:"
testServices = (
- (apache_prefix, twistedcaldav.directory.test.test_apache.Apache ),
(xml_prefix , twistedcaldav.directory.test.test_xmlfile.XMLFile),
)
@@ -65,16 +63,9 @@
"""
Returns an IDirectoryService.
"""
- apacheService = BasicDirectoryService(
- {
- 'realmName' : digestRealm,
- 'userFile' : basicUserFile,
- 'groupFile' : groupFile,
- }
- )
- apacheService.recordTypePrefix = apache_prefix
-
xmlService = XMLDirectoryService({'xmlFile' : xmlFile})
xmlService.recordTypePrefix = xml_prefix
- return AggregateDirectoryService((apacheService, xmlService))
+ augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
+
+ return AggregateDirectoryService((xmlService,))
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,128 +0,0 @@
-##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
-from twistedcaldav.directory.directory import DirectoryService
-
-digestRealm = "Test"
-
-basicUserFile = FilePath(os.path.join(os.path.dirname(__file__), "basic"))
-digestUserFile = FilePath(os.path.join(os.path.dirname(__file__), "digest"))
-groupFile = FilePath(os.path.join(os.path.dirname(__file__), "groups"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class Apache (object):
- recordTypes = set((
- DirectoryService.recordType_users,
- DirectoryService.recordType_groups
- ))
-
- users = {
- "wsanchez": { "password": "foo", "guid": None, "addresses": () },
- "cdaboo" : { "password": "bar", "guid": None, "addresses": () },
- "dreid" : { "password": "baz", "guid": None, "addresses": () },
- "lecroy" : { "password": "quux", "guid": None, "addresses": () },
- }
-
- groups = {
- "managers" : { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "lecroy"),) },
- "grunts" : { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "wsanchez"),
- (DirectoryService.recordType_users, "cdaboo"),
- (DirectoryService.recordType_users, "dreid")) },
- "right_coast": { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "cdaboo"),) },
- "left_coast" : { "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users, "wsanchez"),
- (DirectoryService.recordType_users, "dreid"),
- (DirectoryService.recordType_users, "lecroy")) },
- }
-
- locations = {
- }
-
- resources = {
- }
-
- def service(self):
- return self.serviceClass(
- {
- 'realmName' : digestRealm,
- 'userFile' : self.userFile(),
- 'groupFile' : self.groupFile(),
- }
- )
-
- userFileName = None
-
- def userFile(self):
- if not hasattr(self, "_userFile"):
- if self.userFileName is None:
- raise NotImplementedError("Test subclass needs to specify userFileName.")
- self._userFile = FilePath(self.mktemp())
- basicUserFile.copyTo(self._userFile)
- return self._userFile
-
- def groupFile(self):
- if not hasattr(self, "_groupFile"):
- self._groupFile = FilePath(self.mktemp())
- groupFile.copyTo(self._groupFile)
- return self._groupFile
-
- def test_changedGroupFile(self):
- self.groupFile().open("w").write("grunts: wsanchez\n")
- self.assertEquals(self.recordNames(DirectoryService.recordType_groups), set(("grunts",)))
-
- def test_recordTypes_user(self):
- """
- IDirectoryService.recordTypes(userFile)
- """
- self.assertEquals(set(self.serviceClass({'realmName':digestRealm, 'userFile':self.userFile()}).recordTypes()), set((DirectoryService.recordType_users,)))
-
- userEntry = None
-
- def test_changedUserFile(self):
- if self.userEntry is None:
- raise NotImplementedError("Test subclass needs to specify userEntry.")
- self.userFile().open("w").write(self.userEntry[1])
- self.assertEquals(self.recordNames(DirectoryService.recordType_users), set((self.userEntry[0],)))
-
-class Basic (Apache, twistedcaldav.directory.test.util.BasicTestCase,
- twistedcaldav.directory.test.util.NonCachingTestCase):
- """
- Test Apache-Compatible UserFile/GroupFile directory implementation.
- """
- serviceClass = BasicDirectoryService
-
- userFileName = basicUserFile
- userEntry = ("wsanchez", "wsanchez:Cytm0Bwm7CPJs\n")
-
-class Digest (Apache, twistedcaldav.directory.test.util.DigestTestCase,
- twistedcaldav.directory.test.util.NonCachingTestCase):
- """
- Test Apache-Compatible DigestFile/GroupFile directory implementation.
- """
- serviceClass = DigestDirectoryService
-
- userFileName = digestUserFile
- userEntry = ("wsanchez", "wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c\n")
-
- def test_verifyCredentials_digest(self):
- raise NotImplementedError() # Use super's implementation
- test_verifyCredentials_digest.todo = "unimplemented"
Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/test/test_augment.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_augment.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,178 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.test.util import TestCase
+from twistedcaldav.directory.augment import AugmentXMLDB, AugmentSqliteDB,\
+ AugmentPostgreSQLDB
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.directory.xmlaugmentsparser import XMLAugmentsParser
+import cStringIO
+import os
+
+xmlFile = os.path.join(os.path.dirname(__file__), "augments-test.xml")
+xmlFileDefault = os.path.join(os.path.dirname(__file__), "augments-test-default.xml")
+
+testRecords = (
+ {"guid":"D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "enabled":True, "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False},
+ {"guid":"6423F94A-6B76-4A3A-815B-D52CFD77935D", "enabled":True, "hostedAt":"", "enabledForCalendaring":True, "autoSchedule":False},
+ {"guid":"5A985493-EE2C-4665-94CF-4DFEA3A89500", "enabled":False, "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False},
+ {"guid":"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "enabled":True, "hostedAt":"", "enabledForCalendaring":False, "autoSchedule":False},
+ {"guid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True, "hostedAt":"00001", "enabledForCalendaring":False, "autoSchedule":False},
+ {"guid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True, "hostedAt":"00002", "enabledForCalendaring":False, "autoSchedule":False},
+ {"guid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True, "hostedAt":"00002", "enabledForCalendaring":True, "autoSchedule":True},
+)
+
+testRecordDefault = {"guid":"A4318887-F2C7-4A70-9056-B88CC8DB26F1", "enabled":True, "hostedAt":"00001", "enabledForCalendaring":True, "autoSchedule":False}
+
+class AugmentTests(TestCase):
+
+ @inlineCallbacks
+ def _checkRecord(self, db, items):
+
+ record = (yield db.getAugmentRecord(items["guid"]))
+ self.assertTrue(record is not None)
+
+ for k,v in items.iteritems():
+ self.assertEqual(getattr(record, k), v)
+
+ @inlineCallbacks
+ def _checkNoRecord(self, db, guid):
+
+ record = (yield db.getAugmentRecord(guid))
+ self.assertTrue(record is None)
+
+class AugmentXMLTests(AugmentTests):
+
+ @inlineCallbacks
+ def test_read(self):
+
+ db = AugmentXMLDB((xmlFile,))
+
+ for item in testRecords:
+ yield self._checkRecord(db, item)
+
+ yield self._checkNoRecord(db, "D11F03A0-97EA-48AF-9A6C-FAC7F3975767")
+
+ @inlineCallbacks
+ def test_read_default(self):
+
+ db = AugmentXMLDB((xmlFileDefault,))
+
+ for item in testRecords:
+ yield self._checkRecord(db, item)
+
+ yield self._checkRecord(db, testRecordDefault)
+
+ def test_parseErrors(self):
+
+ db = {}
+ self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO(""), db)
+ self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+<accounts>
+ <foo/>
+</accounts>
+"""), db)
+ self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+<augments>
+ <foo/>
+</augments>
+"""), db)
+ self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+<augments>
+ <record>
+ <enable>true</enable>
+ </record>
+</augments>
+"""), db)
+ self.assertRaises(RuntimeError, XMLAugmentsParser, cStringIO.StringIO("""<?xml version="1.0" encoding="utf-8"?>
+ <record>
+ <guid>admin</guid>
+ <enable>true</enable>
+ <foo/>
+ </record>
+"""), db)
+
+class AugmentSqliteTests(AugmentTests):
+
+ @inlineCallbacks
+ def test_read(self):
+
+ db = AugmentSqliteDB(self.mktemp())
+
+ dbxml = AugmentXMLDB((xmlFile,))
+ for record in dbxml.db.values():
+ yield db.addAugmentRecord(record)
+
+ for item in testRecords:
+ yield self._checkRecord(db, item)
+
+ yield self._checkNoRecord(db, "D11F03A0-97EA-48AF-9A6C-FAC7F3975767")
+
+ @inlineCallbacks
+ def test_read_default(self):
+
+ db = AugmentSqliteDB(self.mktemp())
+
+ dbxml = AugmentXMLDB((xmlFileDefault,))
+ for record in dbxml.db.values():
+ yield db.addAugmentRecord(record)
+
+ for item in testRecords:
+ yield self._checkRecord(db, item)
+
+ yield self._checkRecord(db, testRecordDefault)
+
+class AugmentPostgreSQLTests(AugmentTests):
+
+ @inlineCallbacks
+ def test_read(self):
+
+ db = AugmentPostgreSQLDB("localhost", "augments")
+ yield db.clean()
+
+ dbxml = AugmentXMLDB((xmlFile,))
+ for record in dbxml.db.values():
+ yield db.addAugmentRecord(record)
+
+ for item in testRecords:
+ yield self._checkRecord(db, item)
+
+ yield self._checkNoRecord(db, "D11F03A0-97EA-48AF-9A6C-FAC7F3975767")
+
+ @inlineCallbacks
+ def test_read_default(self):
+
+ db = AugmentPostgreSQLDB("localhost", "augments")
+ yield db.clean()
+
+ dbxml = AugmentXMLDB((xmlFileDefault,))
+ for record in dbxml.db.values():
+ yield db.addAugmentRecord(record)
+
+ for item in testRecords:
+ yield self._checkRecord(db, item)
+
+ yield self._checkRecord(db, testRecordDefault)
+
+try:
+ import pgdb
+except ImportError:
+ AugmentPostgreSQLTests.skip = True
+else:
+ try:
+ db = pgdb.connect(host="localhost", database="augments")
+ except:
+ AugmentPostgreSQLTests.skip = True
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_cachedirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_cachedirectory.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_cachedirectory.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.directory.util import uuidFromName
from uuid import uuid4
+from twistedcaldav.directory.augment import AugmentRecord
class TestDirectoryService (CachingDirectoryService):
@@ -62,8 +63,16 @@
firstName = "",
lastName = "",
emailAddresses = record.get("email"),
+ )
+
+ augmentRecord = AugmentRecord(
+ guid = cacheRecord.guid,
+ enabled=True,
enabledForCalendaring = True,
- )
+ )
+
+ cacheRecord.addAugmentInformation(augmentRecord)
+
self.recordCacheForType(recordType).addRecord(cacheRecord,
indexType, indexKey)
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -21,8 +21,9 @@
from twisted.web2.test.test_server import SimpleRequest
from twistedcaldav import caldavxml
+from twistedcaldav.directory import augment
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
from twistedcaldav.directory.xmlfile import XMLDirectoryService
from twistedcaldav.static import CalendarHomeProvisioningFile
@@ -36,11 +37,12 @@
super(ProvisionedCalendars, self).setUp()
# Setup the initial directory
- self.xmlfile = self.mktemp()
- fd = open(self.xmlfile, "w")
+ self.xmlFile = self.mktemp()
+ fd = open(self.xmlFile, "w")
fd.write(open(xmlFile.path, "r").read())
fd.close()
- self.directoryService = XMLDirectoryService({'xmlFile' : self.xmlfile})
+ self.directoryService = XMLDirectoryService({'xmlFile' : self.xmlFile})
+ augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
# Set up a principals hierarchy for each service we're testing with
name = "principals"
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_guidchange.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -37,11 +37,11 @@
super(ProvisionedPrincipals, self).setUp()
# Setup the initial directory
- self.xmlfile = self.mktemp()
- fd = open(self.xmlfile, "w")
+ self.xmlFile = self.mktemp()
+ fd = open(self.xmlFile, "w")
fd.write(open(xmlFile.path, "r").read())
fd.close()
- self.directoryService = XMLDirectoryService({'xmlFile' : self.xmlfile})
+ self.directoryService = XMLDirectoryService({'xmlFile' : self.xmlFile})
# Set up a principals hierarchy for each service we're testing with
name = "principals"
@@ -78,7 +78,7 @@
def privs1(result):
# Change GUID in record
- fd = open(self.xmlfile, "w")
+ fd = open(self.xmlFile, "w")
fd.write(open(xmlFile.path, "r").read().replace(oldUID, newUID))
fd.close()
fd = None
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
else:
import twisted.web2.auth.digest
import twistedcaldav.directory.test.util
+ from twistedcaldav.directory import augment
from twisted.internet.defer import inlineCallbacks
from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.directory.appleopendirectory import OpenDirectoryRecord
@@ -53,6 +54,7 @@
def setUp(self):
super(OpenDirectory, self).setUp()
self._service = OpenDirectoryService({'node' : "/Search"}, dosetup=False)
+ augment.AugmentService = augment.AugmentXMLDB(xmlFiles=())
def tearDown(self):
for call in self._service._delayedCalls:
@@ -73,7 +75,6 @@
firstName = "Some",
lastName = "User",
emailAddresses = set(("someuser at example.com",)),
- enabledForCalendaring = True,
memberGUIDs = [],
)
self.assertEquals(record.fullName, "")
@@ -90,7 +91,6 @@
firstName = "Some",
lastName = "User",
emailAddresses = set(("someuser at example.com",)),
- enabledForCalendaring = True,
memberGUIDs = [],
)
@@ -111,7 +111,6 @@
firstName = "Some",
lastName = "User",
emailAddresses = set(("someuser at example.com",)),
- enabledForCalendaring = True,
memberGUIDs = [],
)
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectoryschema.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectoryschema.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectoryschema.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,147 +0,0 @@
-##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-try:
- from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
-except ImportError:
- pass
-else:
- from twistedcaldav.test.util import TestCase
-
- class ODResourceInfoParse (TestCase):
-
- plist_good_false = """<?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>com.apple.WhitePagesFramework</key>
- <dict>
- <key>AutoAcceptsInvitation</key>
- <false/>
- <key>Label</key>
- <string>Location</string>
- <key>CalendaringDelegate</key>
- <string>1234-GUID-5678</string>
- <key>ReadOnlyCalendaringDelegate</key>
- <string>1234-GUID-5679</string>
- </dict>
-</dict>
-</plist>
-"""
-
- plist_good_true = """<?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>com.apple.WhitePagesFramework</key>
- <dict>
- <key>AutoAcceptsInvitation</key>
- <true/>
- <key>Label</key>
- <string>Location</string>
- <key>CalendaringDelegate</key>
- <string></string>
- <key>ReadOnlyCalendaringDelegate</key>
- <string></string>
- </dict>
-</dict>
-</plist>
-"""
-
- plist_good_missing = """<?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>com.apple.WhitePagesFramework</key>
- <dict>
- <key>Label</key>
- <string>Location</string>
- </dict>
-</dict>
-</plist>
-"""
-
- plist_bad = """<?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>com.apple.WhitePagesFramework</key>
- <string>bogus</string>
-</dict>
-</plist>
-"""
-
- plist_wrong = """<?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>com.apple.YellowPagesFramework</key>
- <dict>
- <key>AutoAcceptsInvitation</key>
- <true/>
- <key>Label</key>
- <string>Location</string>
- <key>CalendaringDelegate</key>
- <string>1234-GUID-5678</string>
- <key>ReadOnlyCalendaringDelegate</key>
- <string>1234-GUID-5679</string>
- </dict>
-</dict>
-</plist>
-"""
-
- plist_invalid = """<?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>com.apple.WhitePagesFramework</key>
- <string>bogus</string>
- <string>another bogon</string>
-</dict>
-</plist>
-"""
-
- plist_invalid_xml = """<?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>com.apple.WhitePagesFramework</key>
- <string>R&D</string>
-</dict>
-</plist>
-"""
-
- test_bool = (
- (plist_good_false, False, "1234-GUID-5678", "1234-GUID-5679", None),
- (plist_good_true, True, "", "", None),
- (plist_good_missing, False, None, None, None),
- (plist_wrong, False, None, None, None),
- (plist_bad, False, None, None, ValueError),
- (plist_invalid, False, None, None, ValueError),
- (plist_invalid_xml, False, None, None, ValueError),
- )
-
- def test_plists(self):
- service = OpenDirectoryService({'node' : "/Search"}, dosetup=False)
-
- for item in ODResourceInfoParse.test_bool:
- if item[4] is None:
- item1, item2, item3 = service._parseResourceInfo(item[0], "guid", "locations", "name")
- self.assertEqual(item1, item[1])
- self.assertEqual(item2, item[2])
- self.assertEqual(item3, item[3])
- else:
- self.assertRaises(item[4], service._parseResourceInfo, item[0], "guid", "locations", "name")
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -25,11 +25,10 @@
from twistedcaldav.static import CalendarHomeProvisioningFile
from twistedcaldav.config import config
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
+from twistedcaldav.directory import augment, calendaruserproxy
from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.test.test_apache import basicUserFile, digestUserFile, groupFile, digestRealm
from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
from twistedcaldav.directory.principal import DirectoryPrincipalTypeProvisioningResource
from twistedcaldav.directory.principal import DirectoryPrincipalResource
@@ -48,20 +47,6 @@
super(ProvisionedPrincipals, self).setUp()
self.directoryServices = (
- BasicDirectoryService(
- {
- 'realmName' : digestRealm,
- 'userFile' : basicUserFile,
- 'groupFile' : groupFile,
- }
- ),
- DigestDirectoryService(
- {
- 'realmName' : digestRealm,
- 'userFile' : digestUserFile,
- 'groupFile' : groupFile,
- }
- ),
XMLDirectoryService(
{
'xmlFile' : xmlFile,
@@ -81,6 +66,9 @@
self.principalRootResources[directory.__class__.__name__] = provisioningResource
+ augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
+ calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(self.mktemp())
+
def test_hierarchy(self):
"""
DirectoryPrincipalProvisioningResource.listChildren(),
@@ -206,7 +194,7 @@
"""
for provisioningResource, recordType, recordResource, record in self._allRecords():
principal = provisioningResource.principalForRecord(record)
- self.failIf(principal is None)
+ self.failIf(principal is None, msg=str(record))
self.assertEquals(record, principal.record)
def test_principalForCalendarUserAddress(self):
@@ -229,6 +217,7 @@
self.failIf(principal is not None)
# Explicitly check the disabled record
+ provisioningResource = self.principalRootResources['XMLDirectoryService']
self.failIf(provisioningResource.principalForCalendarUserAddress("mailto:nocalendar at example.com") is not None)
self.failIf(provisioningResource.principalForCalendarUserAddress("urn:uuid:543D28BA-F74F-4D5F-9243-B3E3A61171E5") is not None)
self.failIf(provisioningResource.principalForCalendarUserAddress("/principals/users/nocalendar/") is not None)
@@ -434,7 +423,7 @@
def qname(self):
return self.ns, self.name
- provisioningResource = self.principalRootResources['BasicDirectoryService']
+ provisioningResource = self.principalRootResources['XMLDirectoryService']
expected = (
("DAV:", "displayname", "morgen", "fullName", "morgen"),
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,18 +15,19 @@
##
from twistedcaldav.config import config
-import os
-
from twisted.internet.defer import inlineCallbacks
-from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
+from twistedcaldav.directory.calendaruserproxy import ProxySqliteDB,\
+ ProxyPostgreSQLDB
import twistedcaldav.test.util
+from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
+from twistedcaldav.directory import calendaruserproxy
-class ProxyPrincipalDB (twistedcaldav.test.util.TestCase):
+class ProxyPrincipalDBSqlite (twistedcaldav.test.util.TestCase):
"""
Directory service provisioned principals.
"""
- class old_CalendarUserProxyDatabase(CalendarUserProxyDatabase):
+ class old_ProxyDB(ProxySqliteDB):
def _db_version(self):
"""
@@ -34,7 +35,7 @@
"""
return "3"
- def _db_init_data_tables(self, q):
+ def _db_init_data_tables(self):
"""
Initialise the underlying database tables.
@param q: a database cursor to use.
@@ -43,7 +44,7 @@
#
# GROUPS table
#
- q.execute(
+ return self.execute(
"""
create table GROUPS (
GROUPNAME text,
@@ -52,7 +53,7 @@
"""
)
- class new_CalendarUserProxyDatabase(CalendarUserProxyDatabase):
+ class new_ProxyDB(ProxySqliteDB):
def _db_version(self):
"""
@@ -60,7 +61,7 @@
"""
return "11"
- class newer_CalendarUserProxyDatabase(CalendarUserProxyDatabase):
+ class newer_ProxyDB(ProxySqliteDB):
def _db_version(self):
"""
@@ -73,8 +74,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = CalendarUserProxyDatabase(db_path)
+ db = ProxySqliteDB(db_path)
yield db.setGroupMembers("A", ("B", "C", "D",))
membersA = yield db.getMembers("A")
@@ -83,29 +83,28 @@
self.assertEqual(membersA, set(("B", "C", "D",)))
self.assertEqual(membershipsB, set(("A",)))
+ @inlineCallbacks
def test_DBIndexed(self):
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = CalendarUserProxyDatabase(db_path)
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set(("GROUPNAMES", "MEMBERS")))
+ db = ProxySqliteDB(db_path)
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set(("GROUPNAMES", "MEMBERS")))
+ @inlineCallbacks
def test_OldDB(self):
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = self.old_CalendarUserProxyDatabase(db_path)
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set())
+ db = self.old_ProxyDB(db_path)
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set())
@inlineCallbacks
def test_DBUpgrade(self):
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = self.old_CalendarUserProxyDatabase(db_path)
+ db = self.old_ProxyDB(db_path)
yield db.setGroupMembers("A", ("B", "C", "D",))
membersA = yield db.getMembers("A")
@@ -113,19 +112,19 @@
self.assertEqual(membersA, set(("B", "C", "D",)))
self.assertEqual(membershipsB, set(("A",)))
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set())
- db._db_close()
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set())
+ db.close()
db = None
- db = CalendarUserProxyDatabase(db_path)
+ db = ProxySqliteDB(db_path)
membersA = yield db.getMembers("A")
membershipsB = yield db.getMemberships("B")
self.assertEqual(membersA, set(("B", "C", "D",)))
self.assertEqual(membershipsB, set(("A",)))
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set(("GROUPNAMES", "MEMBERS")))
- db._db_close()
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set(("GROUPNAMES", "MEMBERS")))
+ db.close()
db = None
@inlineCallbacks
@@ -133,8 +132,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = self.old_CalendarUserProxyDatabase(db_path)
+ db = self.old_ProxyDB(db_path)
yield db.setGroupMembers("A", ("B", "C", "D",))
membersA = yield db.getMembers("A")
@@ -142,19 +140,19 @@
self.assertEqual(membersA, set(("B", "C", "D",)))
self.assertEqual(membershipsB, set(("A",)))
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set())
- db._db_close()
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set())
+ db.close()
db = None
- db = self.new_CalendarUserProxyDatabase(db_path)
+ db = self.new_ProxyDB(db_path)
membersA = yield db.getMembers("A")
membershipsB = yield db.getMemberships("B")
self.assertEqual(membersA, set(("B", "C", "D",)))
self.assertEqual(membershipsB, set(("A",)))
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set(("GROUPNAMES", "MEMBERS")))
- db._db_close()
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set(("GROUPNAMES", "MEMBERS")))
+ db.close()
db = None
@inlineCallbacks
@@ -162,8 +160,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = self.new_CalendarUserProxyDatabase(db_path)
+ db = self.new_ProxyDB(db_path)
yield db.setGroupMembers("A", ("B", "C", "D",))
membersA = yield db.getMembers("A")
@@ -171,19 +168,19 @@
self.assertEqual(membersA, set(("B", "C", "D",)))
self.assertEqual(membershipsB, set(("A",)))
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set(("GROUPNAMES", "MEMBERS")))
- db._db_close()
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set(("GROUPNAMES", "MEMBERS")))
+ db.close()
db = None
- db = self.newer_CalendarUserProxyDatabase(db_path)
+ db = self.newer_ProxyDB(db_path)
membersA = yield db.getMembers("A")
membershipsB = yield db.getMemberships("B")
self.assertEqual(membersA, set(("B", "C", "D",)))
self.assertEqual(membershipsB, set(("A",)))
- self.assertEqual(set([row[1] for row in db._db_execute("PRAGMA index_list(GROUPS)")]), set(("GROUPNAMES", "MEMBERS")))
- db._db_close()
+ self.assertEqual(set([row[1] for row in (yield db.query("PRAGMA index_list(GROUPS)"))]), set(("GROUPNAMES", "MEMBERS")))
+ db.close()
db = None
@inlineCallbacks
@@ -194,8 +191,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = CalendarUserProxyDatabase(db_path)
+ db = ProxySqliteDB(db_path)
# Do one insert and check the result
yield db.setGroupMembers("A", ("B", "C", "D",))
@@ -235,8 +231,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = CalendarUserProxyDatabase(db_path)
+ db = ProxySqliteDB(db_path)
# Do one insert and check the result
yield db.setGroupMembers("A", ("B", "C", "D",))
@@ -277,8 +272,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = CalendarUserProxyDatabase(db_path)
+ db = ProxySqliteDB(db_path)
# Do one insert and check the result
yield db.setGroupMembers("A", ("B", "C", "D",))
@@ -311,8 +305,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = CalendarUserProxyDatabase(db_path)
+ db = ProxySqliteDB(db_path)
# Do one insert and check the result
yield db.setGroupMembers("A", ("B", "C", "D",))
@@ -353,8 +346,7 @@
# Get the DB
db_path = self.mktemp()
- os.mkdir(db_path)
- db = CalendarUserProxyDatabase(db_path)
+ db = ProxySqliteDB(db_path)
# Do one insert and check the result for the one we will remove
yield db.setGroupMembers("AA", ("BB", "CC", "DD",))
@@ -375,3 +367,234 @@
self.assertEqual(membershipsDD, set())
self.assertEqual(membershipsEE, set(("AA",)))
+class ProxyPrincipalDBPostgreSQL (twistedcaldav.test.util.TestCase):
+ """
+ Directory service provisioned principals.
+ """
+
+ @inlineCallbacks
+ def setUp(self):
+
+ super(ProxyPrincipalDBPostgreSQL, self).setUp()
+ self.db = ProxyPostgreSQLDB(host="localhost", database="proxies")
+ yield self.db.clean()
+
+ @inlineCallbacks
+ def tearDown(self):
+ yield self.db.close()
+ self.db = None
+
+ @inlineCallbacks
+ def test_normalDB(self):
+
+ # Get the DB
+ yield self.db.clean()
+
+ calendaruserproxy.ProxyDBService = self.db
+ loader = XMLCalendarUserProxyLoader("/Volumes/Data/Users/cyrusdaboo/Documents/Development/Apple/eclipse/CalendarServer-3/conf/auth/proxies-test.xml")
+ yield loader.updateProxyDB()
+
+ yield self.db.setGroupMembers("A", ("B", "C", "D",))
+
+ membersA = yield self.db.getMembers("A")
+ membershipsB = yield self.db.getMemberships("B")
+
+ self.assertEqual(membersA, set(("B", "C", "D",)))
+ self.assertEqual(membershipsB, set(("A",)))
+
+ @inlineCallbacks
+ def test_DBIndexed(self):
+
+ # Get the DB
+ yield self.db.clean()
+ self.assertTrue((yield self.db.queryOne("select hasindexes from pg_tables where tablename = 'groups'")))
+
+ @inlineCallbacks
+ def test_cachingDBInsert(self):
+
+ for processType in ("Single", "Combined",):
+ config.ProcessType = processType
+
+ # Get the DB
+ yield self.db.clean()
+
+ # Do one insert and check the result
+ yield self.db.setGroupMembers("A", ("B", "C", "D",))
+
+ membersA = yield self.db.getMembers("A")
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+ membershipsE = yield self.db.getMemberships("E")
+
+ self.assertEqual(membersA, set(("B", "C", "D",)))
+ self.assertEqual(membershipsB, set(("A",)))
+ self.assertEqual(membershipsC, set(("A",)))
+ self.assertEqual(membershipsD, set(("A",)))
+ self.assertEqual(membershipsE, set(()))
+
+ # Change and check the result
+ yield self.db.setGroupMembers("A", ("B", "C", "E",))
+
+ membersA = yield self.db.getMembers("A")
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+ membershipsE = yield self.db.getMemberships("E")
+
+ self.assertEqual(membersA, set(("B", "C", "E",)))
+ self.assertEqual(membershipsB, set(("A",)))
+ self.assertEqual(membershipsC, set(("A",)))
+ self.assertEqual(membershipsD, set())
+ self.assertEqual(membershipsE, set(("A",)))
+
+ @inlineCallbacks
+ def test_cachingDBRemove(self):
+
+ for processType in ("Single", "Combined",):
+ config.ProcessType = processType
+
+ # Get the DB
+ yield self.db.clean()
+
+ # Do one insert and check the result
+ yield self.db.setGroupMembers("A", ("B", "C", "D",))
+ yield self.db.setGroupMembers("X", ("B", "C",))
+
+ membersA = yield self.db.getMembers("A")
+ membersX = yield self.db.getMembers("X")
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+
+ self.assertEqual(membersA, set(("B", "C", "D",)))
+ self.assertEqual(membersX, set(("B", "C",)))
+ self.assertEqual(membershipsB, set(("A", "X",)))
+ self.assertEqual(membershipsC, set(("A", "X",)))
+ self.assertEqual(membershipsD, set(("A",)))
+
+ # Remove and check the result
+ yield self.db.removeGroup("A")
+
+ membersA = yield self.db.getMembers("A")
+ membersX = yield self.db.getMembers("X")
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+
+ self.assertEqual(membersA, set())
+ self.assertEqual(membersX, set(("B", "C",)))
+ self.assertEqual(membershipsB, set("X",))
+ self.assertEqual(membershipsC, set("X",))
+ self.assertEqual(membershipsD, set())
+
+ @inlineCallbacks
+ def test_cachingDBRemoveSpecial(self):
+
+ for processType in ("Single", "Combined",):
+ config.ProcessType = processType
+
+ # Get the DB
+ yield self.db.clean()
+
+ # Do one insert and check the result
+ yield self.db.setGroupMembers("A", ("B", "C", "D",))
+ yield self.db.setGroupMembers("X", ("B", "C",))
+
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+
+ # Remove and check the result
+ yield self.db.removeGroup("A")
+
+ membersA = yield self.db.getMembers("A")
+ membersX = yield self.db.getMembers("X")
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+
+ self.assertEqual(membersA, set())
+ self.assertEqual(membersX, set(("B", "C",)))
+ self.assertEqual(membershipsB, set("X",))
+ self.assertEqual(membershipsC, set("X",))
+ self.assertEqual(membershipsD, set())
+
+ @inlineCallbacks
+ def test_cachingDBRemovePrincipal(self):
+
+ for processType in ("Single", "Combined",):
+ config.ProcessType = processType
+
+ # Get the DB
+ yield self.db.clean()
+
+ # Do one insert and check the result
+ yield self.db.setGroupMembers("A", ("B", "C", "D",))
+ yield self.db.setGroupMembers("X", ("B", "C",))
+
+ membersA = yield self.db.getMembers("A")
+ membersX = yield self.db.getMembers("X")
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+
+ self.assertEqual(membersA, set(("B", "C", "D",)))
+ self.assertEqual(membersX, set(("B", "C",)))
+ self.assertEqual(membershipsB, set(("A", "X",)))
+ self.assertEqual(membershipsC, set(("A", "X",)))
+ self.assertEqual(membershipsD, set(("A",)))
+
+ # Remove and check the result
+ yield self.db.removePrincipal("B")
+
+ membersA = yield self.db.getMembers("A")
+ membersX = yield self.db.getMembers("X")
+ membershipsB = yield self.db.getMemberships("B")
+ membershipsC = yield self.db.getMemberships("C")
+ membershipsD = yield self.db.getMemberships("D")
+
+ self.assertEqual(membersA, set(("C", "D",)))
+ self.assertEqual(membersX, set(("C",)))
+ self.assertEqual(membershipsB, set())
+ self.assertEqual(membershipsC, set(("A", "X",)))
+ self.assertEqual(membershipsD, set(("A",),))
+
+ @inlineCallbacks
+ def test_cachingDBInsertUncached(self):
+
+ for processType in ("Single", "Combined",):
+ config.ProcessType = processType
+
+ # Get the DB
+ yield self.db.clean()
+
+ # Do one insert and check the result for the one we will remove
+ yield self.db.setGroupMembers("AA", ("BB", "CC", "DD",))
+ yield self.db.getMemberships("DD")
+
+ # Change and check the result
+ yield self.db.setGroupMembers("AA", ("BB", "CC", "EE",))
+
+ membersAA = yield self.db.getMembers("AA")
+ membershipsBB = yield self.db.getMemberships("BB")
+ membershipsCC = yield self.db.getMemberships("CC")
+ membershipsDD = yield self.db.getMemberships("DD")
+ membershipsEE = yield self.db.getMemberships("EE")
+
+ self.assertEqual(membersAA, set(("BB", "CC", "EE",)))
+ self.assertEqual(membershipsBB, set(("AA",)))
+ self.assertEqual(membershipsCC, set(("AA",)))
+ self.assertEqual(membershipsDD, set())
+ self.assertEqual(membershipsEE, set(("AA",)))
+
+
+try:
+ import pgdb
+except ImportError:
+ ProxyPrincipalDBPostgreSQL.skip = True
+else:
+ try:
+ db = pgdb.connect(host="localhost", database="proxies")
+ except:
+ ProxyPrincipalDBPostgreSQL.skip = True
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,7 +19,8 @@
from twisted.web2.dav import davxml
from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile,\
+ proxiesFile
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
from twistedcaldav.directory.principal import DirectoryPrincipalResource
from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
@@ -27,16 +28,22 @@
import twistedcaldav.test.util
from twistedcaldav.config import config
+from twistedcaldav.directory import augment, calendaruserproxy
+from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
+import os
-
class ProxyPrincipals (twistedcaldav.test.util.TestCase):
"""
Directory service provisioned principals.
"""
+
+ @inlineCallbacks
def setUp(self):
super(ProxyPrincipals, self).setUp()
self.directoryService = XMLDirectoryService({'xmlFile' : xmlFile})
+ augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
+ calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(self.mktemp())
# Set up a principals hierarchy for each service we're testing with
self.principalRootResources = {}
@@ -49,6 +56,10 @@
self.principalRootResources[self.directoryService.__class__.__name__] = provisioningResource
+ config.DataRoot = self.mktemp()
+ os.mkdir(config.DataRoot)
+ yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
+
def _getPrincipalByShortName(self, type, name):
provisioningResource = self.principalRootResources[self.directoryService.__class__.__name__]
return provisioningResource.principalForShortName(type, name)
@@ -278,7 +289,6 @@
set(["5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1",
"8B4288F6-CC82-491D-8EF9-642EF4F3E7D0"]))
-
@inlineCallbacks
def test_setGroupMemberSetNotifiesPrincipalCaches(self):
class StubCacheNotifier(object):
@@ -295,7 +305,7 @@
oldCacheNotifier = DirectoryPrincipalResource.cacheNotifierFactory
try:
- DirectoryPrincipalResource.cacheNotifierFactory = (lambda _1, _2: notifier)
+ DirectoryPrincipalResource.cacheNotifierFactory = (lambda _1, _2, **kwargs: notifier)
self.assertEquals(notifier.changedCount, 0)
@@ -387,6 +397,7 @@
# Set up the in-memory (non-null) memcacher:
config.ProcessType = "Single"
+ calendaruserproxy.ProxyDBService._memcacher._memcacheProtocol = None
principal = self._getPrincipalByShortName(
DirectoryService.recordType_users, "wsanchez")
db = principal._calendar_user_proxy_index()
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,51 +0,0 @@
-##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-#import twistedcaldav.directory.test.util
-#import twistedcaldav.directory.test.test_xmlfile
-#from twistedcaldav.directory.sqldb import SQLDirectoryService
-
-xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-# class SQLDB (
-# twistedcaldav.directory.test.test_xmlfile.XMLFileBase,
-# twistedcaldav.directory.test.util.BasicTestCase,
-# twistedcaldav.directory.test.util.DigestTestCase
-# ):
-# """
-# Test SQL directory implementation.
-# """
-# def service(self):
-# return SQLDirectoryService(os.getcwd(), self.xmlFile())
-#
-# def test_verifyCredentials_digest(self):
-# super(SQLDB, self).test_verifyCredentials_digest()
-# test_verifyCredentials_digest.todo = ""
-#
-# def test_verifyRealmFromDB(self):
-# # Make sure the database has been initialized with the XML file
-# self.service()
-#
-# # Then get an instance without using the XML file
-# service = SQLDirectoryService(os.getcwd(), None)
-#
-# self.assertEquals(service.realmName, "Test")
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,16 +17,15 @@
import os
from twisted.python.filepath import FilePath
-from twisted.internet.defer import inlineCallbacks
-from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
+from twistedcaldav.directory import augment
from twistedcaldav.directory.directory import DirectoryService
import twistedcaldav.directory.test.util
from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.resourceinfo import ResourceInfoDatabase
-from twistedcaldav.config import config
xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
+augmentsFile = FilePath(os.path.join(os.path.dirname(__file__), "augments.xml"))
+proxiesFile = FilePath(os.path.join(os.path.dirname(__file__), "proxies.xml"))
# FIXME: Add tests for GUID hooey, once we figure out what that means here
@@ -88,6 +87,12 @@
xmlFile.copyTo(self._xmlFile)
return self._xmlFile
+ def augmentsFile(self):
+ if not hasattr(self, "_augmentsFile"):
+ self._augmentsFile = FilePath(self.mktemp())
+ augmentsFile.copyTo(self._augmentsFile)
+ return self._augmentsFile
+
class XMLFile (
XMLFileBase,
twistedcaldav.directory.test.util.BasicTestCase,
@@ -97,7 +102,9 @@
Test XML file based directory implementation.
"""
def service(self):
- return XMLDirectoryService({'xmlFile' : self.xmlFile()}, alwaysStat=True)
+ directory = XMLDirectoryService({'xmlFile' : self.xmlFile()}, alwaysStat=True)
+ augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,))
+ return directory
def test_changedXML(self):
service = self.service()
@@ -130,7 +137,6 @@
set(expectedRecords)
)
- @inlineCallbacks
def test_okAutoSchedule(self):
service = self.service()
@@ -143,11 +149,25 @@
<guid>myoffice</guid>
<password>nimda</password>
<name>Super User</name>
- <auto-schedule/>
</location>
</accounts>
"""
)
+ self.augmentsFile().open("w").write(
+"""<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+<augments>
+ <record>
+ <guid>myoffice</guid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <auto-schedule>true</auto-schedule>
+ </record>
+</augments>
+"""
+ )
+ augment.AugmentService.refresh()
+
for recordType, expectedRecords in (
( DirectoryService.recordType_users , () ),
( DirectoryService.recordType_groups , () ),
@@ -162,8 +182,7 @@
set(r.shortNames[0] for r in service.listRecords(recordType)),
set(expectedRecords)
)
- resourceInfoDatabase = ResourceInfoDatabase(config.DataRoot)
- self.assertTrue((yield resourceInfoDatabase.getAutoSchedule(service.recordWithShortName(DirectoryService.recordType_locations, "my office").guid)))
+ self.assertTrue(service.recordWithShortName(DirectoryService.recordType_locations, "my office").autoSchedule)
def test_okDisableCalendar(self):
@@ -182,11 +201,11 @@
<uid>disabled</uid>
<password>disabled</password>
<name>Disabled</name>
- <disable-calendar/>
</group>
</accounts>
"""
)
+
for recordType, expectedRecords in (
( DirectoryService.recordType_users , () ),
( DirectoryService.recordType_groups , ("enabled", "disabled") ),
@@ -206,50 +225,3 @@
self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, "enabled").enabledForCalendaring)
self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, "disabled").enabledForCalendaring)
- @inlineCallbacks
- def test_okProxies(self):
- service = self.service()
-
- self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
- <user>
- <uid>test</uid>
- <guid>test</guid>
- <password>nimda</password>
- <name>Test</name>
- </user>
- <location>
- <uid>my office</uid>
- <guid>myoffice</guid>
- <password>nimda</password>
- <name>Super User</name>
- <auto-schedule/>
- <proxies>
- <member>test</member>
- </proxies>
- </location>
-</accounts>
-"""
- )
- for recordType, expectedRecords in (
- ( DirectoryService.recordType_users , ("test",) ),
- ( DirectoryService.recordType_groups , () ),
- ( DirectoryService.recordType_locations , ("my office",) ),
- ( DirectoryService.recordType_resources , () ),
- ):
- # Fault records in
- for name in expectedRecords:
- service.recordWithShortName(recordType, name)
-
- self.assertEquals(
- set(r.shortNames[0] for r in service.listRecords(recordType)),
- set(expectedRecords)
- )
- calendarUserProxyDatabase = CalendarUserProxyDatabase(config.DataRoot)
- members = (yield calendarUserProxyDatabase.getMembers("myoffice#calendar-proxy-write"))
- self.assertTrue("test" in members)
- members = (yield calendarUserProxyDatabase.getMemberships("test"))
- self.assertTrue("myoffice#calendar-proxy-write" in members)
-
Modified: CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -27,13 +27,8 @@
from twisted.python.filepath import FilePath
-from twistedcaldav.config import config
from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.log import Logger
-from twistedcaldav.sql import DatabaseError
-from twistedcaldav.directory.directory import DirectoryError
-from twistedcaldav.directory.resourceinfo import ResourceInfoDatabase
-from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
log = Logger()
@@ -52,15 +47,14 @@
ELEMENT_EMAIL_ADDRESS = "email-address"
ELEMENT_MEMBERS = "members"
ELEMENT_MEMBER = "member"
-ELEMENT_AUTOSCHEDULE = "auto-schedule"
-ELEMENT_DISABLECALENDAR = "disable-calendar"
-ELEMENT_PROXIES = "proxies"
-ELEMENT_READ_ONLY_PROXIES = "read-only-proxies"
ATTRIBUTE_REALM = "realm"
ATTRIBUTE_REPEAT = "repeat"
ATTRIBUTE_RECORDTYPE = "type"
+VALUE_TRUE = "true"
+VALUE_FALSE = "false"
+
RECORD_TYPES = {
ELEMENT_USER : DirectoryService.recordType_users,
ELEMENT_GROUP : DirectoryService.recordType_groups,
@@ -75,7 +69,7 @@
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
- def __init__(self, xmlFile):
+ def __init__(self, xmlFile, externalUpdate=True):
if type(xmlFile) is str:
xmlFile = FilePath(xmlFile)
@@ -98,46 +92,7 @@
log.error("Ignoring file %r because it is not a repository builder file" % (self.xmlFile,))
return
self._parseXML(accounts_node)
- try:
- self._updateExternalDatabases()
- except DatabaseError, e:
- raise DirectoryError(e)
- def _updateExternalDatabases(self):
- resourceInfoDatabase = ResourceInfoDatabase(config.DataRoot)
-
- calendarUserProxyDatabase = CalendarUserProxyDatabase(config.DataRoot)
-
- for records in self.items.itervalues():
- for principal in records.itervalues():
-
- resourceInfoDatabase.setAutoScheduleInDatabase(principal.guid,
- principal.autoSchedule)
-
- if principal.proxies:
- proxies = []
- for recordType, uid in principal.proxies:
- record = self.items[recordType].get(uid)
- if record is not None:
- proxies.append(record.guid)
-
- calendarUserProxyDatabase.setGroupMembersInDatabase(
- "%s#calendar-proxy-write" % (principal.guid,),
- proxies
- )
-
- if principal.readOnlyProxies:
- readOnlyProxies = []
- for recordType, uid in principal.readOnlyProxies:
- record = self.items[recordType].get(uid)
- if record is not None:
- readOnlyProxies.append(record.guid)
-
- calendarUserProxyDatabase.setGroupMembersInDatabase(
- "%s#calendar-proxy-read" % (principal.guid,),
- readOnlyProxies
- )
-
def _parseXML(self, node):
"""
Parse the XML root node from the accounts configuration document.
@@ -153,19 +108,6 @@
if item is not None:
item.groups.add(group.shortNames[0])
- def updateProxyFor(proxier):
- # Update proxy membership
- for recordType, shortName in proxier.proxies:
- item = self.items[recordType].get(shortName)
- if item is not None:
- item.proxyFor.add((proxier.recordType, proxier.shortNames[0]))
-
- # Update read-only proxy membership
- for recordType, shortName in proxier.readOnlyProxies:
- item = self.items[recordType].get(shortName)
- if item is not None:
- item.readOnlyProxyFor.add((proxier.recordType, proxier.shortNames[0]))
-
for child in node._get_childNodes():
child_name = child._get_localName()
if child_name is None:
@@ -194,7 +136,6 @@
for records in self.items.itervalues():
for principal in records.itervalues():
updateMembership(principal)
- updateProxyFor(principal)
class XMLAccountRecord (object):
"""
@@ -214,16 +155,6 @@
self.emailAddresses = set()
self.members = set()
self.groups = set()
- self.calendarUserAddresses = set()
- self.autoSchedule = False
- if recordType == DirectoryService.recordType_groups:
- self.enabledForCalendaring = False
- else:
- self.enabledForCalendaring = True
- self.proxies = set()
- self.proxyFor = set()
- self.readOnlyProxies = set()
- self.readOnlyProxyFor = set()
def repeat(self, ctr):
"""
@@ -273,10 +204,6 @@
result.lastName = lastName
result.emailAddresses = emailAddresses
result.members = self.members
- result.autoSchedule = self.autoSchedule
- result.enabledForCalendaring = self.enabledForCalendaring
- result.proxies = self.proxies
- result.readOnlyProxies = self.readOnlyProxies
return result
def parseXML(self, node):
@@ -309,24 +236,11 @@
self.emailAddresses.add(child.firstChild.data.encode("utf-8").lower())
elif child_name == ELEMENT_MEMBERS:
self._parseMembers(child, self.members)
- elif child_name == ELEMENT_AUTOSCHEDULE:
- self.autoSchedule = True
- elif child_name == ELEMENT_DISABLECALENDAR:
- # FIXME: Not sure I see why this restriction is needed. --wsanchez
- ## Only Users or Groups
- #if self.recordType != DirectoryService.recordType_users:
- # raise ValueError("<disable-calendar> element only allowed for Users: %s" % (child_name,))
- self.enabledForCalendaring = False
- elif child_name == ELEMENT_PROXIES:
- self._parseMembers(child, self.proxies)
- elif child_name == ELEMENT_READ_ONLY_PROXIES:
- self._parseMembers(child, self.readOnlyProxies)
else:
raise RuntimeError("Unknown account attribute: %s" % (child_name,))
- if self.enabledForCalendaring:
- for email in self.emailAddresses:
- self.calendarUserAddresses.add("mailto:%s" % (email,))
+ if not self.shortNames:
+ self.shortNames.append(self.guid)
def _parseMembers(self, node, addto):
for child in node._get_childNodes():
Copied: CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/directory/xmlaugmentsparser.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,137 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from xml.etree.ElementTree import ElementTree
+from xml.parsers.expat import ExpatError
+import types
+
+"""
+XML based augment configuration file handling.
+"""
+
+__all__ = [
+ "XMLAugmentsParser",
+]
+
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+ELEMENT_AUGMENTS = "augments"
+ELEMENT_RECORD = "record"
+
+ELEMENT_GUID = "guid"
+ELEMENT_ENABLE = "enable"
+ELEMENT_HOSTEDAT = "hosted-at"
+ELEMENT_ENABLECALENDAR = "enable-calendar"
+ELEMENT_AUTOSCHEDULE = "auto-schedule"
+
+ATTRIBUTE_REPEAT = "repeat"
+
+VALUE_TRUE = "true"
+VALUE_FALSE = "false"
+
+ELEMENT_AUGMENTRECORD_MAP = {
+ ELEMENT_GUID: "guid",
+ ELEMENT_ENABLE: "enabled",
+ ELEMENT_HOSTEDAT: "hostedAt",
+ ELEMENT_ENABLECALENDAR: "enabledForCalendaring",
+ ELEMENT_AUTOSCHEDULE: "autoSchedule",
+}
+
+class XMLAugmentsParser(object):
+ """
+ XML augments configuration file parser.
+ """
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+ def __init__(self, xmlFile, items):
+
+ self.items = items
+ self.xmlFile = xmlFile
+
+ # Read in XML
+ try:
+ tree = ElementTree(file=self.xmlFile)
+ except ExpatError, e:
+ log.error("Unable to parse file '%s' because: %s" % (self.xmlFile, e,), raiseException=RuntimeError)
+
+ # Verify that top-level element is correct
+ augments_node = tree.getroot()
+ if augments_node.tag != ELEMENT_AUGMENTS:
+ log.error("Ignoring file '%s' because it is not a augments file" % (self.xmlFile,), raiseException=RuntimeError)
+
+ self._parseXML(augments_node)
+
+ def _parseXML(self, rootnode):
+ """
+ Parse the XML root node from the augments configuration document.
+ @param rootnode: the L{Element} to parse.
+ """
+ for child in rootnode.getchildren():
+
+ if child.tag != ELEMENT_RECORD:
+ log.error("Unknown augment type: '%s' in augment file: '%s'" % (child.tag, self.xmlFile,), raiseException=RuntimeError)
+
+ repeat = int(child.get(ATTRIBUTE_REPEAT, "1"))
+
+ fields = {}
+ for node in child.getchildren():
+
+ if node.tag in (
+ ELEMENT_GUID,
+ ELEMENT_HOSTEDAT,
+ ):
+ fields[node.tag] = node.text
+ elif node.tag in (
+ ELEMENT_ENABLE,
+ ELEMENT_ENABLECALENDAR,
+ ELEMENT_AUTOSCHEDULE,
+ ):
+ fields[node.tag] = node.text == VALUE_TRUE
+ else:
+ log.error("Invalid element '%s' in augment file: '%s'" % (node.tag, self.xmlFile,), raiseException=RuntimeError)
+
+ # Must have at least a guid
+ if ELEMENT_GUID not in fields:
+ log.error("Invalid record '%s' without a guid in augment file: '%s'" % (child, self.xmlFile,), raiseException=RuntimeError)
+
+ if repeat > 1:
+ for i in xrange(1, repeat+1):
+ self.buildRecord(fields, i)
+ else:
+ self.buildRecord(fields)
+
+ def buildRecord(self, fields, count=None):
+
+ from twistedcaldav.directory.augment import AugmentRecord
+
+ def expandCount(value, count):
+
+ if type(value) in types.StringTypes:
+ return value % (count,) if count and "%" in value else value
+ elif type(value) == set:
+ return set([item % (count,) if count and "%" in item else item for item in value])
+ else:
+ return value
+
+ actualFields = {}
+ for k,v in fields.iteritems():
+ actualFields[ELEMENT_AUGMENTRECORD_MAP[k]] = expandCount(v, count)
+
+ record = AugmentRecord(**actualFields)
+ self.items[record.guid] = record
Modified: CalendarServer/trunk/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlfile.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlfile.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
from twisted.web2.auth.digest import DigestedCredentials
from twisted.python.filepath import FilePath
+from twistedcaldav.directory import augment
from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.directory.cachingdirectory import CachingDirectoryService,\
CachingDirectoryRecord
@@ -85,17 +86,29 @@
xmlPrincipal = xmlPrincipal,
)
+ record = XMLDirectoryRecord(
+ service = self,
+ recordType = recordType,
+ shortNames = tuple(xmlPrincipal.shortNames),
+ xmlPrincipal = xmlPrincipal,
+ )
+
+ # Look up augment information
+ # TODO: this needs to be deferred but for now we hard code the deferred result because
+ # we know it is completing immediately.
+ d = augment.AugmentService.getAugmentRecord(record.guid)
+ d.addCallback(lambda x:record.addAugmentInformation(x))
+
matched = False
if indexType == self.INDEX_TYPE_GUID:
- matched = indexKey == xmlPrincipal.guid
+ matched = indexKey == record.guid
elif indexType == self.INDEX_TYPE_SHORTNAME:
- matched = indexKey in xmlPrincipal.shortNames
+ matched = indexKey in record.shortNames
elif indexType == self.INDEX_TYPE_CUA:
matched = indexKey in record.calendarUserAddresses
if matched:
- self.recordCacheForType(recordType).addRecord(
- record, indexType, indexKey)
+ self.recordCacheForType(recordType).addRecord(record, indexType, indexKey)
def recordsMatchingFields(self, fields, operand="or", recordType=None):
# Default, brute force method search of underlying XML data
@@ -191,7 +204,6 @@
firstName = xmlPrincipal.firstName,
lastName = xmlPrincipal.lastName,
emailAddresses = xmlPrincipal.emailAddresses,
- enabledForCalendaring = xmlPrincipal.enabledForCalendaring,
)
self.password = xmlPrincipal.password
@@ -207,9 +219,10 @@
yield self.service.recordWithShortName(DirectoryService.recordType_groups, shortName)
def verifyCredentials(self, credentials):
- if isinstance(credentials, UsernamePassword):
- return credentials.password == self.password
- if isinstance(credentials, DigestedCredentials):
- return credentials.checkPassword(self.password)
+ if self.enabled:
+ if isinstance(credentials, UsernamePassword):
+ return credentials.password == self.password
+ if isinstance(credentials, DigestedCredentials):
+ return credentials.checkPassword(self.password)
return super(XMLDirectoryRecord, self).verifyCredentials(credentials)
Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/extensions.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.test.test_extensions -*-
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -442,17 +442,18 @@
for record in records:
resource = principalCollection.principalForRecord(record)
- matchingResources.append(resource)
+ if resource:
+ matchingResources.append(resource)
+
+ # We've determined this is a matching resource
+ matchcount += 1
+ if clientLimit is not None and matchcount >= clientLimit:
+ resultsWereLimited = ("client", matchcount)
+ break
+ if matchcount >= max_number_of_matches:
+ resultsWereLimited = ("server", matchcount)
+ break
- # We've determined this is a matching resource
- matchcount += 1
- if clientLimit is not None and matchcount >= clientLimit:
- resultsWereLimited = ("client", matchcount)
- break
- if matchcount >= max_number_of_matches:
- resultsWereLimited = ("server", matchcount)
- break
-
# Generate the response
responses = []
for resource in matchingResources:
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -1242,8 +1242,8 @@
timezone_refs = set()
timezones = set()
got_master = False
- #got_override = False
- #master_recurring = False
+ #got_override = False
+ #master_recurring = False
for subcomponent in self.subcomponents():
# Disallowed in CalDAV-Access-08, section 4.1
@@ -1291,15 +1291,14 @@
raise ValueError(msg)
else:
got_master = True
- # master_recurring =
- subcomponent.hasProperty("RRULE") or subcomponent.hasProperty("RDATE")
+ # master_recurring = subcomponent.hasProperty("RRULE") or subcomponent.hasProperty("RDATE")
else:
pass # got_override = True
# Check that if an override is present then the master is recurring
# Leopard iCal sometimes does this for overridden instances that an Attendee receives and
# it creates a "fake" (invalid) master. We are going to skip this test here. Instead implicit
- # scheduling with verify the validity of the components and raise if they don't make sense.
+ # scheduling will verify the validity of the components and raise if they don't make sense.
# If no scheduling is happening then we allow this - that may cause other clients to choke.
# If it does we will have to reinstate this check but only after we have checked for implicit.
# UNCOMMENT OUT master_recurring AND got_override ASSIGNMENTS ABOVE
@@ -2089,7 +2088,7 @@
if dataValue.find(dropboxPrefix) != -1:
self.removeProperty(attachment)
- def normalizeCalendarUserAddresses(self, lookupFunction):
+ def normalizeCalendarUserAddresses(self, lookupFunction, toUUID=True):
"""
Do the ORGANIZER/ATTENDEE property normalization.
@@ -2111,9 +2110,48 @@
if guid is None:
continue
- # Always re-write value to urn:uuid
- prop.setValue("urn:uuid:%s" % (guid,))
+ # Get any EMAIL parameter
+ oldemail = prop.params().get("EMAIL", (None,))[0]
+ if oldemail:
+ oldemail = "mailto:%s" % (oldemail,)
+ if toUUID:
+ # Always re-write value to urn:uuid
+ prop.setValue("urn:uuid:%s" % (guid,))
+
+ # If it is already a non-UUID address leave it be
+ elif cuaddr.startswith("urn:uuid:"):
+ if oldemail:
+ # Use the EMAIL parameter if it exists
+ newaddr = oldemail
+ else:
+ # Pick the first mailto, or failing that the first http, or failing that the first one
+ first_mailto = None
+ first_http = None
+ first = None
+ for addr in cuaddrs:
+ if addr.startswith("mailto:"):
+ first_mailto = addr
+ break
+ elif addr.startswith("http:"):
+ if not first_http:
+ first_http = addr
+ elif not first:
+ first = addr
+
+ if first_mailto:
+ newaddr = first_mailto
+ elif first_http:
+ newaddr = first_http
+ elif first:
+ newaddr = first
+ else:
+ newaddr = None
+
+ # Make the change
+ if newaddr:
+ prop.setValue(newaddr)
+
# Always re-write the CN parameter
if name:
prop.params()["CN"] = [name,]
@@ -2123,12 +2161,8 @@
except KeyError:
pass
- # Re-write the EMAIL if its value no longer
- # matches
- oldemail = prop.params().get("EMAIL", (None,))[0]
- if oldemail:
- oldemail = "mailto:%s" % (oldemail,)
- if oldemail is None or oldemail not in cuaddrs:
+ # Re-write the EMAIL if its value no longer matches
+ if oldemail and oldemail not in cuaddrs or oldemail is None and toUUID:
if cuaddr.startswith("mailto:") and cuaddr in cuaddrs:
email = cuaddr[7:]
else:
Modified: CalendarServer/trunk/twistedcaldav/index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/index.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/index.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -828,7 +828,7 @@
assert resource.isCalendarCollection(), "non-calendar collection resource %s has no index." % (resource,)
super(Index, self).__init__(resource)
- if config.Memcached['ClientEnabled']:
+ if config.Memcached.Pools.Default.ClientEnabled:
self.reserver = MemcachedUIDReserver(self)
else:
Modified: CalendarServer/trunk/twistedcaldav/log.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/log.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/log.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -352,8 +352,10 @@
#
# Attach methods to Logger
#
- def log_emit(self, message, __level__=level, **kwargs):
+ def log_emit(self, message, __level__=level, raiseException=None, **kwargs):
self.emit(__level__, message, **kwargs)
+ if raiseException:
+ raise raiseException(message)
def will_emit(self, __level__=level):
return self.willLogAtLevel(__level__)
@@ -369,8 +371,10 @@
#
# Attach methods to LoggingMixIn
#
- def log_emit(self, message, __level__=level, **kwargs):
+ def log_emit(self, message, __level__=level, raiseException=None, **kwargs):
self.logger.emit(__level__, message, **kwargs)
+ if raiseException:
+ raise raiseException(message)
def will_emit(self=log_emit, __level__=level):
return self.logger.willLogAtLevel(__level__)
Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/mail.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -544,15 +544,10 @@
def makeService(self, options):
- if config.Memcached.ClientEnabled:
- memcachepool.installPool(
- IPv4Address(
- "TCP",
- config.Memcached.BindAddress,
- config.Memcached.Port,
- ),
- config.Memcached.MaxClients,
- )
+ memcachepool.installPools(
+ config.Memcached.Pools,
+ config.Memcached.MaxClients,
+ )
multiService = service.MultiService()
Modified: CalendarServer/trunk/twistedcaldav/memcachepool.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcachepool.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/memcachepool.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
##
from twisted.python.failure import Failure
+from twisted.internet.address import IPv4Address
from twisted.internet.defer import Deferred, fail
from twisted.internet.protocol import ReconnectingClientFactory
@@ -370,22 +371,46 @@
@ivar _cachePool: A saved cachePool.
"""
_cachePool = None
+ _cachePoolHandle = "Default"
def getCachePool(self):
if self._cachePool is None:
- return defaultCachePool()
+ return defaultCachePool(self._cachePoolHandle)
return self._cachePool
-_memCachePool = None
+_memCachePools = {} # Maps a name to a pool object
+_memCachePoolHandler = {} # Maps a handler id to a named pool
-def installPool(serverAddress, maxClients=5, reactor=None):
- global _memCachePool
- _memCachePool = MemCachePool(serverAddress,
+def installPools(pools, maxClients=5, reactor=None):
+
+ for name, pool in pools.items():
+ if pool["ClientEnabled"]:
+ _installPool(
+ name,
+ pool["HandleCacheTypes"],
+ IPv4Address(
+ "TCP",
+ pool["BindAddress"],
+ pool["Port"],
+ ),
+ maxClients,
+ reactor,
+ )
+
+def _installPool(name, handleTypes, serverAddress, maxClients=5, reactor=None):
+
+ pool = MemCachePool(serverAddress,
maxClients=maxClients,
reactor=None)
+ _memCachePools[name] = pool
-def defaultCachePool():
- return _memCachePool
+ for handle in handleTypes:
+ _memCachePoolHandler[handle] = pool
+
+def defaultCachePool(name):
+ if name not in _memCachePoolHandler:
+ name = "Default"
+ return _memCachePoolHandler[name]
Modified: CalendarServer/trunk/twistedcaldav/memcacheprops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcacheprops.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/memcacheprops.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2009 Apple Computer, Inc. All rights reserved.
+# Copyright (c) 2009-2010 Apple Computer, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -60,7 +60,9 @@
log.info("Instantiating memcache connection for MemcachePropertyCollection")
- MemcachePropertyCollection._memcacheClient = MemcacheClientFactory.getClient(["%s:%s" % (config.Memcached.BindAddress, config.Memcached.Port)],
+ MemcachePropertyCollection._memcacheClient = MemcacheClientFactory.getClient([
+ "%s:%s" % (config.Memcached.Pools.Default.BindAddress, config.Memcached.Pools.Default.Port)
+ ],
debug=0,
pickleProtocol=2,
)
Modified: CalendarServer/trunk/twistedcaldav/memcacher.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/memcacher.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/memcacher.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -107,6 +107,7 @@
assert len(namespace) <= Memcacher.NAMESPACE_MAX_LENGTH, "Memcacher namespace must be less than or equal to %s characters long" % (Memcacher.NAMESPACE_MAX_LENGTH,)
self._memcacheProtocol = None
+ self._cachePoolHandle = namespace
self._namespace = namespace
self._pickle = pickle
self._noInvalidation = no_invalidation
@@ -116,7 +117,7 @@
if self._memcacheProtocol is not None:
return self._memcacheProtocol
- if config.Memcached['ClientEnabled']:
+ if config.Memcached.Pools.Default.ClientEnabled:
self._memcacheProtocol = self.getCachePool()
elif config.ProcessType == "Single" or self._noInvalidation or self.allowTestCache:
Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -290,7 +290,7 @@
@inlineCallbacks
def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
- excludeuid=None, organizer=None, same_calendar_user=False,
+ excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False,
servertoserver=False):
"""
Run a free busy report on the specified calendar collection
@@ -313,7 +313,7 @@
# TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
if not servertoserver:
try:
- yield calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),))
+ yield calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), principal=organizerPrincipal)
except AccessDeniedError:
returnValue(matchtotal)
@@ -371,7 +371,7 @@
# TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
if not servertoserver:
try:
- yield child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces)
+ yield child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces, principal=organizerPrincipal)
except AccessDeniedError:
continue
Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/notify.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2008 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -1301,13 +1301,10 @@
#
# Configure Memcached Client Pool
#
- if config.Memcached.ClientEnabled:
- memcachepool.installPool(
- IPv4Address(
- 'TCP',
- config.Memcached.BindAddress,
- config.Memcached.Port),
- config.Memcached.MaxClients)
+ memcachepool.installPools(
+ config.Memcached.Pools,
+ config.Memcached.MaxClients,
+ )
multiService = service.MultiService()
Copied: CalendarServer/trunk/twistedcaldav/partitions.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/partitions.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/partitions.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/partitions.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,71 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.python.plistlib import readPlist
+from twistedcaldav.client.pool import installPool
+from twistedcaldav.log import Logger
+
+"""
+Collection of classes for managing partition information for a group of servers.
+"""
+
+log = Logger()
+
+class Partitions(object):
+
+ def __init__(self):
+
+ self.clear()
+
+ def clear(self):
+ self.partitions = {}
+ self.ownUID = ""
+ self.maxClients = 5
+
+ def readConfig(self, plistpath):
+ try:
+ dataDict = readPlist(plistpath)
+ except (IOError, OSError):
+ log.error("Configuration file does not exist or is inaccessible: %s" % (self._configFileName,))
+ return
+
+ for partition in dataDict.get("partitions", ()):
+ uid = partition.get("uid", None)
+ url = partition.get("url", None)
+ if uid and url:
+ self.partitions[uid] = url
+
+ def setSelfPartition(self, uid):
+ self.ownUID = uid
+
+ def setMaxClients(self, maxClients):
+ self.maxClients = maxClients
+
+ def getPartitionURL(self, uid):
+ # When the UID matches this server return an empty string
+ return self.partitions.get(uid, None) if uid != self.ownUID else ""
+
+ def installReverseProxies(self):
+
+ for partition, url in self.partitions.iteritems():
+ if partition != self.ownUID:
+ installPool(
+ partition,
+ url,
+ self.maxClients,
+ )
+
+partitions = Partitions()
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -905,7 +905,7 @@
))
elif name == "auto-schedule":
- autoSchedule = (yield self.getAutoSchedule())
+ autoSchedule = self.getAutoSchedule()
returnValue(customxml.AutoSchedule("true" if autoSchedule else "false"))
result = (yield super(CalendarPrincipalResource, self).readProperty(property, request))
@@ -916,8 +916,8 @@
"%r is not a WebDAVElement instance" % (property,)
)
- if property.qname() == (caldav_namespace, "auto-schedule"):
- self.setAutoSchedule(autoSchedule)
+ #if property.qname() == (caldav_namespace, "auto-schedule"):
+ # self.setAutoSchedule(autoSchedule)
return super(CalendarPrincipalResource, self).writeProperty(property, request)
Modified: CalendarServer/trunk/twistedcaldav/scheduling/addressmapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/addressmapping.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/addressmapping.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,7 +22,8 @@
from twistedcaldav.scheduling.imip import ScheduleViaIMip
from twistedcaldav.scheduling.ischedule import ScheduleViaISchedule
from twistedcaldav.scheduling.cuaddress import LocalCalendarUser,\
- RemoteCalendarUser, EmailCalendarUser, InvalidCalendarUser
+ RemoteCalendarUser, EmailCalendarUser, InvalidCalendarUser,\
+ PartitionedCalendarUser
from twisted.internet.defer import inlineCallbacks, returnValue
__all__ = [
@@ -49,17 +50,14 @@
@inlineCallbacks
def getCalendarUser(self, cuaddr, principal):
- # If we have a principal always treat the user as local
+ # If we have a principal always treat the user as local or partitioned
if principal:
- returnValue(LocalCalendarUser(cuaddr, principal))
+ returnValue(LocalCalendarUser(cuaddr, principal) if principal.locallyHosted() else PartitionedCalendarUser(cuaddr, principal))
# Get the type
cuaddr_type = (yield self.getCalendarUserServiceType(cuaddr))
if cuaddr_type == DeliveryService.serviceType_caldav:
- if principal:
- returnValue(LocalCalendarUser(cuaddr, principal))
- else:
- returnValue(InvalidCalendarUser(cuaddr))
+ returnValue(InvalidCalendarUser(cuaddr))
elif cuaddr_type == DeliveryService.serviceType_ischedule:
returnValue(RemoteCalendarUser(cuaddr))
elif cuaddr_type == DeliveryService.serviceType_imip:
Modified: CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/caldav.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/caldav.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -38,7 +38,8 @@
from twistedcaldav.log import Logger
from twistedcaldav.method import report_common
from twistedcaldav.resource import isCalendarCollectionResource
-from twistedcaldav.scheduling.cuaddress import LocalCalendarUser, RemoteCalendarUser
+from twistedcaldav.scheduling.cuaddress import LocalCalendarUser, RemoteCalendarUser,\
+ PartitionedCalendarUser
from twistedcaldav.scheduling.delivery import DeliveryService
from twistedcaldav.scheduling.itip import iTIPRequestStatus
from twistedcaldav.scheduling.processing import ImplicitProcessor, ImplicitProcessorException
@@ -73,7 +74,7 @@
if cuaddr.startswith("mailto:") and config.Scheduling[cls.serviceType()]["EmailDomain"]:
addr = cuaddr[7:].split("?")[0]
domain = config.Scheduling[cls.serviceType()]["EmailDomain"]
- account, addrDomain = addr.split("@")
+ _ignore_account, addrDomain = addr.split("@")
if addrDomain == domain:
return True
@@ -98,7 +99,7 @@
uid = self.scheduler.calendar.resourceUID()
organizerPrincipal = None
- if type(self.scheduler.organizer) in (LocalCalendarUser,):
+ if type(self.scheduler.organizer) in (LocalCalendarUser, PartitionedCalendarUser,):
organizerPrincipal = davxml.Principal(davxml.HRef(self.scheduler.organizer.principal.principalURL()))
for recipient in self.recipients:
@@ -125,7 +126,7 @@
# Different behavior for free-busy vs regular invite
if self.freebusy:
- yield self.generateFreeBusyResponse(recipient, self.responses, organizerProp, uid)
+ yield self.generateFreeBusyResponse(recipient, self.responses, organizerProp, organizerPrincipal, uid)
else:
yield self.generateResponse(recipient, self.responses)
@@ -196,7 +197,7 @@
returnValue(True)
@inlineCallbacks
- def generateFreeBusyResponse(self, recipient, responses, organizerProp, uid):
+ def generateFreeBusyResponse(self, recipient, responses, organizerProp, organizerPrincipal, uid):
# Extract the ATTENDEE property matching current recipient from the calendar data
cuas = recipient.principal.calendarUserAddresses()
@@ -208,6 +209,7 @@
fbresult = (yield self.generateAttendeeFreeBusyResponse(
recipient,
organizerProp,
+ organizerPrincipal,
uid,
attendeeProp,
remote,
@@ -222,7 +224,7 @@
returnValue(True)
@inlineCallbacks
- def generateAttendeeFreeBusyResponse(self, recipient, organizerProp, uid, attendeeProp, remote):
+ def generateAttendeeFreeBusyResponse(self, recipient, organizerProp, organizerPrincipal, uid, attendeeProp, remote):
# Find the current recipients calendar-free-busy-set
fbset = (yield recipient.principal.calendarFreeBusyURIs(self.scheduler.request))
@@ -261,6 +263,7 @@
matchtotal,
excludeuid = self.scheduler.excludeUID,
organizer = self.scheduler.organizer.cuaddr,
+ organizerPrincipal = organizerPrincipal,
same_calendar_user = same_calendar_user,
servertoserver=remote,
))
Modified: CalendarServer/trunk/twistedcaldav/scheduling/cuaddress.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/cuaddress.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/cuaddress.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
__all__ = [
"LocalCalendarUser",
+ "PartitionedCalendarUser",
"RemoteCalendarUser",
"EmailCalendarUser",
"InvalidCalendarUser",
@@ -43,6 +44,15 @@
def __str__(self):
return "Local calendar user: %s" % (self.cuaddr,)
+class PartitionedCalendarUser(CalendarUser):
+ def __init__(self, cuaddr, principal):
+ self.cuaddr = cuaddr
+ self.principal = principal
+ self.serviceType = DeliveryService.serviceType_ischedule
+
+ def __str__(self):
+ return "Partitioned calendar user: %s" % (self.cuaddr,)
+
class RemoteCalendarUser(CalendarUser):
def __init__(self, cuaddr):
self.cuaddr = cuaddr
Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -26,17 +26,17 @@
from twistedcaldav import caldavxml
from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.customxml import TwistedSchedulingObjectResource
+from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
from twistedcaldav.ical import Property
from twistedcaldav.log import Logger
from twistedcaldav.method import report_common
from twistedcaldav.scheduling import addressmapping
from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser,\
- LocalCalendarUser
+ LocalCalendarUser, PartitionedCalendarUser
from twistedcaldav.scheduling.icaldiff import iCalDiff
from twistedcaldav.scheduling.itip import iTipGenerator, iTIPRequestStatus
from twistedcaldav.scheduling.scheduler import CalDAVScheduler
from twistedcaldav.scheduling.utils import getCalendarObjectForPrincipals
-from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
__all__ = [
"ImplicitScheduler",
@@ -316,7 +316,7 @@
# Get some useful information from the calendar
yield self.extractCalendarData()
- self.attendee = attendee.cuaddr
+ self.originator = self.attendee = attendee.cuaddr
self.attendeePrincipal = attendee.principal
result = (yield self.scheduleWithOrganizer())
@@ -327,6 +327,8 @@
def extractCalendarData(self):
# Get the originator who is the authenticated user
+ # TODO: the originator actually needs to be the owner of the calendar collection not the authenticated
+ # principal, who might be a proxy or admin
self.originatorPrincipal = None
self.originator = ""
authz_principal = self.resource.currentPrincipal(self.request).children[0]
@@ -877,6 +879,10 @@
calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForPrincipals(self.request, self.organizerPrincipal, self.uid))
if calendar_resource:
self.organizer_calendar = calendar_resource.iCalendar()
+ elif isinstance(self.organizerAddress, PartitionedCalendarUser):
+ # For partitioning where the organizer is on a different node, we will assume that the attendee's copy
+ # of the event is up to date and "authoritative". So we pretend that is the organizer copy
+ self.organizer_calendar = self.oldcalendar
def isAttendeeChangeInsignificant(self):
"""
Modified: CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/ischedule.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@
from twisted.web2 import responsecode
from twisted.web2.client.http import ClientRequest
from twisted.web2.client.http import HTTPClientProtocol
-from twisted.web2.dav.util import davXMLFromStream
+from twisted.web2.dav.util import davXMLFromStream, joinURL
from twisted.web2.http import HTTPError
from twisted.web2.http_headers import Headers
from twisted.web2.http_headers import MimeType
@@ -36,9 +36,12 @@
from twistedcaldav.config import config
from twistedcaldav.log import Logger
from twistedcaldav.scheduling.delivery import DeliveryService
-from twistedcaldav.scheduling.ischeduleservers import IScheduleServers
+from twistedcaldav.scheduling.ischeduleservers import IScheduleServers,\
+ IScheduleServerRecord
from twistedcaldav.scheduling.itip import iTIPRequestStatus
from twistedcaldav.util import utf8String
+from twistedcaldav.scheduling.cuaddress import RemoteCalendarUser,\
+ PartitionedCalendarUser
import OpenSSL
@@ -66,7 +69,7 @@
# Do default match
return super(ScheduleViaISchedule, cls).matchCalendarUserAddress(cuaddr)
- def generateSchedulingResponses(self):
+ def generateSchedulingResponses(self, refreshOnly=False):
"""
Generate scheduling responses for remote recipients.
"""
@@ -76,8 +79,13 @@
groups = {}
servermgr = IScheduleServers()
for recipient in self.recipients:
- # Map the recipient's domain to a server
- server = servermgr.mapDomain(recipient.domain)
+ if isinstance(recipient, RemoteCalendarUser):
+ # Map the recipient's domain to a server
+ server = servermgr.mapDomain(recipient.domain)
+ elif isinstance(recipient, PartitionedCalendarUser):
+ server = self._getServerForPartitionedUser(recipient)
+ else:
+ assert False, "Incorrect calendar user address class"
if not server:
# Cannot do server-to-server for this recipient.
err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-allowed")))
@@ -104,19 +112,32 @@
# rather than serialize them.
deferreds = []
for server, recipients in groups.iteritems():
- requestor = IScheduleRequest(self.scheduler, server, recipients, self.responses)
+ requestor = IScheduleRequest(self.scheduler, server, recipients, self.responses, refreshOnly)
deferreds.append(requestor.doRequest())
return DeferredList(deferreds)
+ def _getServerForPartitionedUser(self, recipient):
+
+ if not hasattr(self, "partitionedServers"):
+ self.partitionedServers = {}
+
+ partition = recipient.principal.hostedURL()
+ if partition not in self.partitionedServers:
+ self.partitionedServers[partition] = IScheduleServerRecord(uri=joinURL(partition, "/ischedule"))
+ self.partitionedServers[partition].unNormalizeAddresses = False
+
+ return self.partitionedServers[partition]
+
class IScheduleRequest(object):
- def __init__(self, scheduler, server, recipients, responses):
+ def __init__(self, scheduler, server, recipients, responses, refreshOnly=False):
self.scheduler = scheduler
self.server = server
self.recipients = recipients
self.responses = responses
+ self.refreshOnly = refreshOnly
self._generateHeaders()
self._prepareData()
@@ -152,12 +173,17 @@
def _generateHeaders(self):
self.headers = Headers()
self.headers.setHeader('Host', utf8String(self.server.host + ":%s" % (self.server.port,)))
- self.headers.addRawHeader('Originator', utf8String(self.scheduler.originator.cuaddr))
+
+ # The Originator must be the ORGANIZER (for a request) or ATTENDEE (for a reply)
+ self.headers.addRawHeader('Originator', utf8String(self.scheduler.organizer.cuaddr if self.scheduler.isiTIPRequest else self.scheduler.attendee))
self._doAuthentication()
for recipient in self.recipients:
self.headers.addRawHeader('Recipient', utf8String(recipient.cuaddr))
self.headers.setHeader('Content-Type', MimeType("text", "calendar", params={"charset":"utf-8"}))
+ if self.refreshOnly:
+ self.headers.addRawHeader("X-CALENDARSERVER-ITIP-REFRESHONLY", "T")
+
def _doAuthentication(self):
if self.server.authentication and self.server.authentication[0] == "basic":
self.headers.setHeader(
@@ -166,8 +192,22 @@
)
def _prepareData(self):
- self.data = str(self.scheduler.calendar)
+ if self.server.unNormalizeAddresses and self.scheduler.method == "PUT":
+ def lookupFunction(cuaddr):
+ principal = self.scheduler.resource.principalForCalendarUserAddress(cuaddr)
+ if principal is None:
+ return (None, None, None)
+ else:
+ return (principal.record.fullName.decode("utf-8"),
+ principal.record.guid,
+ principal.record.calendarUserAddresses)
+ normalizedCalendar = self.scheduler.calendar.duplicate()
+ normalizedCalendar.normalizeCalendarUserAddresses(lookupFunction, toUUID=False)
+ else:
+ normalizedCalendar = self.scheduler.calendar
+ self.data = str(normalizedCalendar)
+
def _parseResponse(self, xml):
# Check for correct root element
Modified: CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/ischeduleservers.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -123,7 +123,7 @@
"""
Contains server-to-server details.
"""
- def __init__(self):
+ def __init__(self, uri=None):
"""
@param recordType: record type for directory entry.
"""
@@ -133,6 +133,11 @@
self.allow_to = True
self.domains = []
self.client_hosts = []
+ self.unNormalizeAddresses = True
+
+ if uri:
+ self.uri = uri
+ self._parseDetails()
def parseXML(self, node):
for child in node._get_childNodes():
Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -283,7 +283,7 @@
raise ImplicitProcessorException(iTIPRequestStatus.NO_USER_SUPPORT)
log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - new processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
- autoprocessed = (yield self.recipient.principal.getAutoSchedule())
+ autoprocessed = self.recipient.principal.getAutoSchedule()
new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr, autoprocessing=autoprocessed)
name = md5(str(new_calendar) + str(time.time()) + default.fp.path).hexdigest() + ".ics"
@@ -306,7 +306,7 @@
result = (True, autoprocessed, changes,)
else:
# Processing update to existing event
- autoprocessed = (yield self.recipient.principal.getAutoSchedule())
+ autoprocessed = self.recipient.principal.getAutoSchedule()
new_calendar, rids = iTipProcessing.processRequest(self.message, self.recipient_calendar, self.recipient.cuaddr, autoprocessing=autoprocessed)
if new_calendar:
@@ -367,7 +367,7 @@
else:
# Need to check for auto-respond attendees. These need to suppress the inbox message
# if the cancel is processed.
- autoprocessed = (yield self.recipient.principal.getAutoSchedule())
+ autoprocessed = self.recipient.principal.getAutoSchedule()
# Check to see if this is a cancel of the entire event
processed_message, delete_original, rids = iTipProcessing.processCancel(self.message, self.recipient_calendar, autoprocessing=autoprocessed)
@@ -506,7 +506,7 @@
tr.start = makeTimedUTC(instance.start)
tr.end = makeTimedUTC(instance.end)
- yield report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid)
+ yield report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid, servertoserver=True)
# If any fbinfo entries exist we have an overlap
if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
Modified: CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/scheduler.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2008 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -36,7 +36,8 @@
from twistedcaldav.scheduling import addressmapping
from twistedcaldav.scheduling.caldav import ScheduleViaCalDAV
from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser,\
- LocalCalendarUser, RemoteCalendarUser, EmailCalendarUser
+ LocalCalendarUser, RemoteCalendarUser, EmailCalendarUser,\
+ PartitionedCalendarUser
from twistedcaldav.scheduling.imip import ScheduleViaIMip
from twistedcaldav.scheduling.ischedule import ScheduleViaISchedule
from twistedcaldav.scheduling.ischeduleservers import IScheduleServers
@@ -45,6 +46,7 @@
import itertools
import re
import socket
+import urlparse
"""
CalDAV/Server-to-Server scheduling behavior.
@@ -70,6 +72,8 @@
self.recipients = None
self.calendar = None
self.organizer = None
+ self.attendee = None
+ self.isiTIPRequest = None
self.timeRange = None
self.excludeUID = None
self.fakeTheResult = False
@@ -247,7 +251,7 @@
def checkOriginator(self):
raise NotImplementedError
- def checkRecipient(self):
+ def checkRecipients(self):
raise NotImplementedError
def checkOrganizer(self):
@@ -282,6 +286,27 @@
log.err("X-CALENDARSERVER-ACCESS not allowed in a calendar component %s request: %s" % (self.method, self.calendar,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (calendarserver_namespace, "no-access-restrictions")))
+ # Determine iTIP method mode
+ if self.calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER"):
+ self.isiTIPRequest = True
+
+ elif self.calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
+ self.isiTIPRequest = False
+
+ # Verify that there is a single ATTENDEE property
+ attendees = self.calendar.getAttendees()
+
+ # Must have only one
+ if len(attendees) != 1:
+ log.err("Wrong number of ATTENDEEs in calendar data: %s" % (self.calendardata,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ self.attendee = attendees[0]
+
+ else:
+ msg = "Unknown iTIP METHOD: %s" % (self.calendar.propertyValue("METHOD"),)
+ log.err(msg)
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description=msg))
+
def checkForFreeBusy(self):
if not hasattr(self, "isfreebusy"):
if (self.calendar.propertyValue("METHOD") == "REQUEST") and (self.calendar.mainType() == "VFREEBUSY"):
@@ -362,6 +387,7 @@
# Loop over each recipient and aggregate into lists by service types.
caldav_recipients = []
+ partitioned_recipients = []
remote_recipients = []
imip_recipients = []
for recipient in self.recipients:
@@ -372,6 +398,9 @@
elif isinstance(recipient, LocalCalendarUser):
caldav_recipients.append(recipient)
+ elif isinstance(recipient, PartitionedCalendarUser):
+ partitioned_recipients.append(recipient)
+
elif isinstance(recipient, RemoteCalendarUser):
remote_recipients.append(recipient)
@@ -386,6 +415,10 @@
if caldav_recipients:
yield self.generateLocalSchedulingResponses(caldav_recipients, responses, freebusy)
+ # Now process partitioned recipients
+ if partitioned_recipients:
+ yield self.generateRemoteSchedulingResponses(partitioned_recipients, responses, freebusy, getattr(self.request, 'doing_attendee_refresh', False))
+
# To reduce chatter, we suppress certain messages
if not getattr(self.request, 'suppressRefresh', False):
@@ -409,14 +442,14 @@
requestor = ScheduleViaCalDAV(self, recipients, responses, freebusy)
return requestor.generateSchedulingResponses()
- def generateRemoteSchedulingResponses(self, recipients, responses, freebusy):
+ def generateRemoteSchedulingResponses(self, recipients, responses, freebusy, refreshOnly=False):
"""
Generate scheduling responses for remote recipients.
"""
# Create the scheduler and run it.
requestor = ScheduleViaISchedule(self, recipients, responses, freebusy)
- return requestor.generateSchedulingResponses()
+ return requestor.generateSchedulingResponses(refreshOnly)
def generateIMIPSchedulingResponses(self, recipients, responses, freebusy):
"""
@@ -499,10 +532,10 @@
inbox = None
inboxURL = principal.scheduleInboxURL()
if inboxURL:
- inbox = (yield self.request.locateResource(inboxURL))
+ inbox = (yield self.request.locateResource(inboxURL)) if principal.locallyHosted() else "dummy"
if inbox:
- results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL))
+ results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL) if principal.locallyHosted() else PartitionedCalendarUser(recipient, principal))
else:
log.err("No schedule inbox for principal: %s" % (principal,))
results.append(InvalidCalendarUser(recipient))
@@ -559,17 +592,8 @@
Only local attendees are allowed for message originating from this server.
"""
- # Verify that there is a single ATTENDEE property
- attendees = self.calendar.getAttendees()
-
- # Must have only one
- if len(attendees) != 1:
- log.err("Wrong number of ATTENDEEs in calendar data: %s" % (self.calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
- attendee = attendees[0]
-
# Attendee's Outbox MUST be the request URI
- attendeePrincipal = self.resource.principalForCalendarUserAddress(attendee)
+ attendeePrincipal = self.resource.principalForCalendarUserAddress(self.attendee)
if attendeePrincipal:
if self.doingPOST and attendeePrincipal.scheduleOutboxURL() != self.request.uri:
log.err("ATTENDEE in calendar data does not match owner of Outbox: %s" % (self.calendar,))
@@ -584,16 +608,12 @@
"""
# Prevent spoofing of ORGANIZER with specific METHODs when local
- if self.calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER"):
+ if self.isiTIPRequest:
self.checkOrganizerAsOriginator()
# Prevent spoofing when doing reply-like METHODs
- elif self.calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
+ else:
self.checkAttendeeAsOriginator()
-
- else:
- log.err("Unknown iTIP METHOD for security checks: %s" % (self.calendar.propertyValue("METHOD"),))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description="Unknown iTIP METHOD for security checks"))
def finalChecks(self):
"""
@@ -618,8 +638,62 @@
else:
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid iTIP message for implicit scheduling"))
-class IScheduleScheduler(Scheduler):
+class RemoteScheduler(Scheduler):
+ def checkOrganizer(self):
+ """
+ Delay ORGANIZER check until we know what their role is.
+ """
+ pass
+
+ @inlineCallbacks
+ def checkRecipients(self):
+ """
+ Check the validity of the Recipient header values. These must all be local as there
+ is no concept of server-to-server relaying.
+ """
+
+ results = []
+ for recipient in self.recipients:
+ # Get the principal resource for this recipient
+ principal = self.resource.principalForCalendarUserAddress(recipient)
+
+ # If no principal we may have a remote recipient but we should check whether
+ # the address is one that ought to be on our server and treat that as a missing
+ # user. Also if server-to-server is not enabled then remote addresses are not allowed.
+ if principal is None:
+ localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(recipient))
+ if localUser:
+ log.err("No principal for calendar user address: %s" % (recipient,))
+ else:
+ log.err("Unknown calendar user address: %s" % (recipient,))
+ results.append(InvalidCalendarUser(recipient))
+ else:
+ # Map recipient to their inbox
+ inbox = None
+ inboxURL = principal.scheduleInboxURL()
+ if inboxURL:
+ inbox = (yield self.request.locateResource(inboxURL)) if principal.locallyHosted() else "dummy"
+
+ if inbox:
+ results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL) if principal.locallyHosted() else PartitionedCalendarUser(recipient, principal))
+ else:
+ log.err("No schedule inbox for principal: %s" % (principal,))
+ results.append(InvalidCalendarUser(recipient))
+
+ self.recipients = results
+
+class IScheduleScheduler(RemoteScheduler):
+
+ def loadFromRequestHeaders(self):
+ """
+ Load Originator and Recipient from request headers.
+ """
+ super(IScheduleScheduler, self).loadFromRequestHeaders()
+
+ if self.request.headers.getRawHeaders('x-calendarserver-itip-refreshonly', ("F"))[0] == "T":
+ self.request.doing_attendee_refresh = 1
+
def checkAuthorization(self):
# Must have an unauthenticated user
if self.resource.currentPrincipal(self.request) != davxml.Principal(davxml.Unauthenticated()):
@@ -636,11 +710,21 @@
originatorPrincipal = self.resource.principalForCalendarUserAddress(self.originator)
localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
if originatorPrincipal or localUser:
- log.err("Cannot use originator that is on this server: %s" % (self.originator,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+ if originatorPrincipal.locallyHosted():
+ log.err("Cannot use originator that is on this server: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+ else:
+ self.originator = PartitionedCalendarUser(self.originator, originatorPrincipal)
+ #self._validPartitionServer()
else:
self.originator = RemoteCalendarUser(self.originator)
-
+ self._validiScheduleServer()
+
+ def _validiScheduleServer(self):
+ """
+ Check the validity of the iSchedule host.
+ """
+
# We will only accept originator in known domains.
servermgr = IScheduleServers()
server = servermgr.mapDomain(self.originator.domain)
@@ -684,49 +768,38 @@
log.err("Originator not on allowed server: %s" % (self.originator,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
- @inlineCallbacks
- def checkRecipients(self):
+ def _validPartitionServer(self, principal):
"""
- Check the validity of the Recipient header values. These must all be local as there
- is no concept of server-to-server relaying.
+ Check the validity of the partitioned host.
"""
-
- results = []
- for recipient in self.recipients:
- # Get the principal resource for this recipient
- principal = self.resource.principalForCalendarUserAddress(recipient)
-
- # If no principal we may have a remote recipient but we should check whether
- # the address is one that ought to be on our server and treat that as a missing
- # user. Also if server-to-server is not enabled then remote addresses are not allowed.
- if principal is None:
- localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(recipient))
- if localUser:
- log.err("No principal for calendar user address: %s" % (recipient,))
- else:
- log.err("Unknown calendar user address: %s" % (recipient,))
- results.append(InvalidCalendarUser(recipient))
- else:
- # Map recipient to their inbox
- inbox = None
- inboxURL = principal.scheduleInboxURL()
- if inboxURL:
- inbox = (yield self.request.locateResource(inboxURL))
- if inbox:
- results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL))
- else:
- log.err("No schedule inbox for principal: %s" % (principal,))
- results.append(InvalidCalendarUser(recipient))
+ # Extract expected host/port
+ expected_uri = principal.hostedURL()
+ expected_uri = urlparse.urlparse(expected_uri)
+
+ # Get the request IP and map to hostname.
+ clientip = self.request.remoteAddr.host
- self.recipients = results
+ # First compare as dotted IP
+ matched = False
+ if clientip == expected_uri.hostname:
+ matched = True
+ else:
+ # Now do hostname lookup
+ try:
+ host, aliases, _ignore_ips = socket.gethostbyaddr(clientip)
+ for hostname in itertools.chain((host,), aliases):
+ # Try host match
+ if hostname == expected_uri.hostname:
+ matched = True
+ break
+ except socket.herror, e:
+ log.debug("iSchedule cannot lookup client ip '%s': %s" % (clientip, str(e),))
+
+ if not matched:
+ log.err("Originator not on allowed server: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
- def checkOrganizer(self):
- """
- Delay ORGANIZER check until we know what their role is.
- """
- pass
-
@inlineCallbacks
def checkOrganizerAsOriginator(self):
"""
@@ -738,8 +811,13 @@
if organizer:
organizerPrincipal = self.resource.principalForCalendarUserAddress(organizer)
if organizerPrincipal:
- log.err("Invalid ORGANIZER in calendar data: %s" % (self.calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+ if organizerPrincipal.locallyHosted():
+ log.err("Invalid ORGANIZER in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+ else:
+ # Check that the origin server is the correct partition
+ self.organizer = PartitionedCalendarUser(organizer, organizerPrincipal)
+ self._validPartitionServer(self.organizer.principal)
else:
localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(organizer))
if localUser:
@@ -758,22 +836,16 @@
Only local attendees are allowed for message originating from this server.
"""
- # Verify that there is a single ATTENDEE property
- attendees = self.calendar.getAttendees()
-
- # Must have only one
- if len(attendees) != 1:
- log.err("Wrong number of ATTENDEEs in calendar data: %s" % (self.calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
- attendee = attendees[0]
-
# Attendee cannot be local.
- attendeePrincipal = self.resource.principalForCalendarUserAddress(attendee)
+ attendeePrincipal = self.resource.principalForCalendarUserAddress(self.attendee)
if attendeePrincipal:
- log.err("Invalid ATTENDEE in calendar data: %s" % (self.calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ if attendeePrincipal.locallyHosted():
+ log.err("Invalid ATTENDEE in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ else:
+ self._validPartitionServer(attendeePrincipal)
else:
- localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(attendee))
+ localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.attendee))
if localUser:
log.err("Unknown ATTENDEE in calendar data: %s" % (self.calendar,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
@@ -826,13 +898,25 @@
pass
-class IMIPScheduler(Scheduler):
+class IMIPScheduler(RemoteScheduler):
def checkAuthorization(self):
pass
- def checkOrganizer(self):
- pass
+ @inlineCallbacks
+ def checkOriginator(self):
+ """
+ Check the validity of the Originator header.
+ """
+
+ # For remote requests we do not allow the originator to be a local user or one within our domain.
+ originatorPrincipal = self.resource.principalForCalendarUserAddress(self.originator)
+ localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
+ if originatorPrincipal or localUser:
+ log.err("Cannot use originator that is on this server: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+ else:
+ self.originator = RemoteCalendarUser(self.originator)
def checkOrganizerAsOriginator(self):
pass
@@ -856,60 +940,7 @@
# TODO: verify this is the right response:
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
- @inlineCallbacks
- def checkOriginator(self):
- """
- Check the validity of the Originator header.
- """
-
- # For remote requests we do not allow the originator to be a local user or one within our domain.
- originatorPrincipal = self.resource.principalForCalendarUserAddress(self.originator)
- localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
- if originatorPrincipal or localUser:
- log.err("Cannot use originator that is on this server: %s" % (self.originator,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
- else:
- self.originator = RemoteCalendarUser(self.originator)
- @inlineCallbacks
- def checkRecipients(self):
- """
- Check the validity of the Recipient header values. These must all be local as there
- is no concept of server-to-server relaying.
- """
-
- results = []
- for recipient in self.recipients:
- # Get the principal resource for this recipient
- principal = self.resource.principalForCalendarUserAddress(recipient)
-
- # If no principal we may have a remote recipient but we should check whether
- # the address is one that ought to be on our server and treat that as a missing
- # user. Also if server-to-server is not enabled then remote addresses are not allowed.
- if principal is None:
- localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(recipient))
- if localUser:
- log.err("No principal for calendar user address: %s" % (recipient,))
- else:
- log.err("Unknown calendar user address: %s" % (recipient,))
- results.append(InvalidCalendarUser(recipient))
- else:
- # Map recipient to their inbox
- inbox = None
- inboxURL = principal.scheduleInboxURL()
- if inboxURL:
- inbox = (yield self.request.locateResource(inboxURL))
-
- if inbox:
- results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL))
- else:
- log.err("No schedule inbox for principal: %s" % (principal,))
- results.append(InvalidCalendarUser(recipient))
-
- self.recipients = results
-
-
-
class ScheduleResponseResponse (Response):
"""
ScheduleResponse L{Response} object.
Modified: CalendarServer/trunk/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -23,12 +23,14 @@
Get a copy of the event for a principal.
"""
- result = {}
- result["resource"] = None
- result["resource_name"] = None
- result["calendar_collection"] = None
- result["calendar_collection_uri"] = None
- if principal:
+ result = {
+ "resource": None,
+ "resource_name": None,
+ "calendar_collection": None,
+ "calendar_collection_uri": None,
+ }
+
+ if principal and principal.locallyHosted():
# Get principal's calendar-home
calendar_home = principal.calendarHome()
Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/static.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -54,11 +54,12 @@
from twisted.web2.dav.noneprops import NonePropertyStore
from twisted.web2.dav.resource import AccessDeniedError
from twisted.web2.dav.resource import davPrivilegeSet
-from twisted.web2.dav.util import parentForURL, bindMethods
+from twisted.web2.dav.util import parentForURL, bindMethods, joinURL
from twistedcaldav import caldavxml
from twistedcaldav import customxml
from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.client.reverseproxy import ReverseProxyResource
from twistedcaldav.config import config
from twistedcaldav.customxml import TwistedCalendarAccessProperty, TwistedScheduleMatchETags
from twistedcaldav.extensions import DAVFile, CachingPropertyStore
@@ -736,60 +737,77 @@
assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
- childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
- child = self.homeResourceClass(childPath.path, self, record)
-
- if not child.exists():
- self.provision()
-
- if not childPath.parent().isdir():
- childPath.parent().makedirs()
-
- for oldPath in (
- # Pre 2.0: All in one directory
- self.fp.child(name),
- # Pre 1.2: In types hierarchy instead of the GUID hierarchy
- self.parent.getChild(record.recordType).fp.child(record.shortNames[0]),
- ):
- if oldPath.exists():
- # The child exists at an old location. Move to new location.
- log.msg("Moving calendar home from old location %r to new location %r." % (oldPath, childPath))
- try:
- oldPath.moveTo(childPath)
- except (OSError, IOError), e:
- log.err("Error moving calendar home %r: %s" % (oldPath, e))
+ if record.locallyHosted():
+ childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
+ child = self.homeResourceClass(childPath.path, self, record)
+
+ if not child.exists():
+ self.provision()
+
+ if not childPath.parent().isdir():
+ childPath.parent().makedirs()
+
+ for oldPath in (
+ # Pre 2.0: All in one directory
+ self.fp.child(name),
+ # Pre 1.2: In types hierarchy instead of the GUID hierarchy
+ self.parent.getChild(record.recordType).fp.child(record.shortNames[0]),
+ ):
+ if oldPath.exists():
+ # The child exists at an old location. Move to new location.
+ log.msg("Moving calendar home from old location %r to new location %r." % (oldPath, childPath))
+ try:
+ oldPath.moveTo(childPath)
+ except (OSError, IOError), e:
+ log.err("Error moving calendar home %r: %s" % (oldPath, e))
+ raise HTTPError(StatusResponse(
+ responsecode.INTERNAL_SERVER_ERROR,
+ "Unable to move calendar home."
+ ))
+ child.fp.changed()
+ break
+ else:
+ #
+ # NOTE: provisionDefaultCalendars() returns a deferred, which we are ignoring.
+ # The result being that the default calendars will be present at some point
+ # in the future, not necessarily right now, and we don't have a way to wait
+ # on that to finish.
+ #
+ child.provisionDefaultCalendars()
+
+ #
+ # Try to work around the above a little by telling the client that something
+ # when wrong temporarily if the child isn't provisioned right away.
+ #
+ if not child.exists():
raise HTTPError(StatusResponse(
- responsecode.INTERNAL_SERVER_ERROR,
- "Unable to move calendar home."
+ responsecode.SERVICE_UNAVAILABLE,
+ "Provisioning calendar home."
))
- child.fp.changed()
- break
- else:
- #
- # NOTE: provisionDefaultCalendars() returns a deferred, which we are ignoring.
- # The result being that the default calendars will be present at some point
- # in the future, not necessarily right now, and we don't have a way to wait
- # on that to finish.
- #
- child.provisionDefaultCalendars()
+
+ assert child.exists()
+
+ else:
+ childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
+ child = CalendarHomeReverseProxyFile(childPath.path, self, record)
- #
- # Try to work around the above a little by telling the client that something
- # when wrong temporarily if the child isn't provisioned right away.
- #
- if not child.exists():
- raise HTTPError(StatusResponse(
- responsecode.SERVICE_UNAVAILABLE,
- "Provisioning calendar home."
- ))
-
- assert child.exists()
-
return child
def createSimilarFile(self, path):
raise HTTPError(responsecode.NOT_FOUND)
+class CalendarHomeReverseProxyFile(ReverseProxyResource):
+
+ def __init__(self, path, parent, record):
+ self.path = path
+ self.parent = parent
+ self.record = record
+
+ super(CalendarHomeReverseProxyFile, self).__init__(self.record.hostedAt)
+
+ def url(self):
+ return joinURL(self.parent.url(), self.record.uid)
+
class CalendarHomeFile (PropfindCacheMixin, AutoProvisioningFileMixIn, DirectoryCalendarHomeResource, CalDAVFile):
"""
Calendar home collection resource.
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -28,6 +28,7 @@
ConfigProvider, ConfigurationError, config, _mergeData, )
from twistedcaldav.log import (
Logger, clearLogLevels, setLogLevelForNamespace, InvalidLogLevelError, )
+from twistedcaldav.partitions import partitions
from twistedcaldav.util import (
KeychainAccessError, KeychainPasswordNotFound, getPasswordFromKeychain, )
@@ -41,12 +42,37 @@
},
"twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
"node": "/Search",
- "restrictEnabledRecords": False,
- "restrictToGroup": "",
"cacheTimeout": 30,
},
}
+DEFAULT_AUGMENT_PARAMS = {
+ "twistedcaldav.directory.augment.AugmentXMLDB": {
+ "xmlFiles": ["/etc/caldavd/augments.xml",],
+ },
+ "twistedcaldav.directory.augment.AugmentSqliteDB": {
+ "dbpath": "/etc/caldavd/augments.sqlite",
+ },
+ "twistedcaldav.directory.augment.AugmentPostgreSQLDB": {
+ "host": "localhost",
+ "database": "augments",
+ "user": "",
+ "password": "",
+ },
+}
+
+DEFAULT_PROXYDB_PARAMS = {
+ "twistedcaldav.directory.calendaruserproxy.ProxySqliteDB": {
+ "dbpath": "/etc/caldavd/proxies.sqlite",
+ },
+ "twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB": {
+ "host": "localhost",
+ "database": "proxies",
+ "user": "",
+ "password": "",
+ },
+}
+
DEFAULT_CONFIG = {
# Note: Don't use None values below; that confuses the command-line parser.
@@ -101,6 +127,25 @@
},
#
+ # Augment service
+ #
+ # Augments for the directory service records to add calendar specific attributes.
+ #
+ "AugmentService": {
+ "type": "twistedcaldav.directory.augment.AugmentXMLDB",
+ "params": DEFAULT_AUGMENT_PARAMS["twistedcaldav.directory.augment.AugmentXMLDB"],
+ },
+
+ #
+ # Proxies
+ #
+ "ProxyDBService": {
+ "type": "twistedcaldav.directory.calendaruserproxy.ProxySqliteDB",
+ "params": DEFAULT_PROXYDB_PARAMS["twistedcaldav.directory.calendaruserproxy.ProxySqliteDB"],
+ },
+ "ProxyLoadFromFile": "", # Allows for initialization of the proxy database from an XML file
+
+ #
# Special principals
#
"AdminPrincipals": [], # Principals with "DAV:all" access (relative URLs)
@@ -316,6 +361,16 @@
},
#
+ # Partitioning
+ #
+ "Partitioning" : {
+ "Enabled": False, # Partitioning enabled or not
+ "ServerPartitionID": "", # Unique ID for this server's partition instance.
+ "PartitionConfigFile": "/etc/caldavd/partitions.plist", # File path for partition information
+ "MaxClients": 5, # Pool size for connections to each partition
+ },
+
+ #
# Performance tuning
#
@@ -378,10 +433,26 @@
"Memcached": {
"MaxClients": 5,
- "ClientEnabled": True,
- "ServerEnabled": True,
- "BindAddress": "127.0.0.1",
- "Port": 11211,
+ "Pools": {
+ "Default": {
+ "ClientEnabled": True,
+ "ServerEnabled": True,
+ "BindAddress": "127.0.0.1",
+ "Port": 11211,
+ "HandleCacheTypes": [
+ "Default",
+ ]
+ },
+# "ProxyDB": {
+# "ClientEnabled": True,
+# "ServerEnabled": True,
+# "BindAddress": "127.0.0.1",
+# "Port": 11211,
+# "HandleCacheTypes": [
+# "ProxyDB", "PrincipalToken",
+# ]
+# },
+ },
"memcached": "memcached", # Find in PATH
"MaxMemory": 0, # Megabytes
"Options": [],
@@ -460,6 +531,20 @@
if param not in DEFAULT_SERVICE_PARAMS[configDict.DirectoryService.type]:
del configDict.DirectoryService.params[param]
+def _postUpdateAugmentService(configDict):
+ if configDict.AugmentService.type in DEFAULT_AUGMENT_PARAMS:
+ for param in tuple(configDict.AugmentService.params):
+ if param not in DEFAULT_AUGMENT_PARAMS[configDict.AugmentService.type]:
+ log.warn("Parameter %s is not supported by service %s" % (param, configDict.AugmentService.type))
+ del configDict.AugmentService.params[param]
+
+def _postUpdateProxyDBService(configDict):
+ if configDict.ProxyDBService.type in DEFAULT_PROXYDB_PARAMS:
+ for param in tuple(configDict.ProxyDBService.params):
+ if param not in DEFAULT_PROXYDB_PARAMS[configDict.ProxyDBService.type]:
+ log.warn("Parameter %s is not supported by service %s" % (param, configDict.ProxyDBService.type))
+ del configDict.ProxyDBService.params[param]
+
def _updateACLs(configDict):
#
# Base resource ACLs
@@ -555,9 +640,8 @@
try:
if "DefaultLogLevel" in configDict:
level = configDict["DefaultLogLevel"]
- if not level:
- level = "warn"
- setLogLevelForNamespace(None, level)
+ if level:
+ setLogLevelForNamespace(None, level)
if "LogLevels" in configDict:
for namespace in configDict["LogLevels"]:
@@ -650,19 +734,35 @@
# The password doesn't exist in the keychain.
log.info("iMIP %s password not found in keychain" %
(direction,))
-
+
+def _updatePartitions(configDict):
+ #
+ # Partitions
+ #
+
+ if configDict.Partitioning.Enabled:
+ partitions.setSelfPartition(configDict.Partitioning.ServerPartitionID)
+ partitions.setMaxClients(configDict.Partitioning.MaxClients)
+ partitions.readConfig(configDict.Partitioning.PartitionConfigFile)
+ partitions.installReverseProxies()
+ else:
+ partitions.clear()
+
PRE_UPDATE_HOOKS = (
_preUpdateDirectoryService,
)
POST_UPDATE_HOOKS = (
_updateHostName,
_postUpdateDirectoryService,
+ _postUpdateAugmentService,
+ _postUpdateProxyDBService,
_updateACLs,
_updateRejectClients,
_updateDropBox,
_updateLogLevels,
_updateNotifications,
_updateScheduling,
+ _updatePartitions,
)
def _cleanup(configDict, defaultDict):
Modified: CalendarServer/trunk/twistedcaldav/test/test_cache.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_cache.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/test/test_cache.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -336,7 +336,7 @@
self.tokens['/principals/__uids__/cdaboo/'] = 'principalToken0'
self.tokens['/principals/__uids__/dreid/'] = 'principalTokenX'
- def _getToken(uri):
+ def _getToken(uri, cachePoolHandle=None):
return succeed(self.tokens.get(uri))
self.rc._tokenForURI = _getToken
Modified: CalendarServer/trunk/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_config.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/test/test_config.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -159,22 +159,15 @@
self.assertEquals(config.DirectoryService.type, "twistedcaldav.directory.appleopendirectory.OpenDirectoryService")
self.assertNotIn("xmlFile", config.DirectoryService.params)
self.assertEquals(config.DirectoryService.params.node, "/Search")
- self.assertEquals(config.DirectoryService.params.restrictEnabledRecords, False)
def testDirectoryService_newParam(self):
self.assertEquals(config.DirectoryService.type, "twistedcaldav.directory.xmlfile.XMLDirectoryService")
self.assertEquals(config.DirectoryService.params.xmlFile, "/etc/caldavd/accounts.xml")
config.update({"DirectoryService": {"type": "twistedcaldav.directory.appleopendirectory.OpenDirectoryService"}})
- config.update({"DirectoryService": {"params": {
- "restrictEnabledRecords": True,
- "restrictToGroup": "12345",
- }}})
self.assertEquals(config.DirectoryService.type, "twistedcaldav.directory.appleopendirectory.OpenDirectoryService")
self.assertEquals(config.DirectoryService.params.node, "/Search")
- self.assertEquals(config.DirectoryService.params.restrictEnabledRecords, True)
- self.assertEquals(config.DirectoryService.params.restrictToGroup, "12345")
def testDirectoryService_unknownType(self):
self.assertEquals(config.DirectoryService.type, "twistedcaldav.directory.xmlfile.XMLDirectoryService")
@@ -236,6 +229,9 @@
"""
Logging module configures properly.
"""
+ config.setDefaults(DEFAULT_CONFIG)
+ config.reload()
+
self.assertEquals(logLevelForNamespace(None), "warn")
self.assertEquals(logLevelForNamespace("some.namespace"), "warn")
Copied: CalendarServer/trunk/twistedcaldav/test/test_database.py (from rev 4957, CalendarServer/branches/users/cdaboo/partition-4464/twistedcaldav/test/test_database.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_database.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_database.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -0,0 +1,208 @@
+##
+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin
+import twistedcaldav.test.util
+
+from twisted.internet.defer import inlineCallbacks
+
+import os
+import time
+
+class Database (twistedcaldav.test.util.TestCase):
+ """
+ Test abstract SQL DB class
+ """
+
+ class TestDB(ADBAPISqliteMixin, AbstractADBAPIDatabase):
+
+ def __init__(self, path, persistent=False, version="1"):
+ self.version = version
+ self.dbpath = path
+ super(Database.TestDB, self).__init__("sqlite", "sqlite3", (path,), persistent, cp_min=3, cp_max=3)
+
+ def _db_version(self):
+ """
+ @return: the schema version assigned to this index.
+ """
+ return self.version
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return "TESTTYPE"
+
+ def _db_init_data_tables(self):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+
+ #
+ # TESTTYPE table
+ #
+ return self._db_execute(
+ """
+ create table TESTTYPE (
+ KEY text unique,
+ VALUE text
+ )
+ """
+ )
+
+ def _db_remove_data_tables(self):
+ return self._db_execute("drop table TESTTYPE")
+
+ class TestDBRecreateUpgrade(TestDB):
+
+ class RecreateDBException(Exception):
+ pass
+ class UpgradeDBException(Exception):
+ pass
+
+ def __init__(self, path, persistent=False):
+ super(Database.TestDBRecreateUpgrade, self).__init__(path, persistent, version="2")
+
+ def _db_recreate(self):
+ raise self.RecreateDBException()
+
+ class TestDBCreateIndexOnUpgrade(TestDB):
+
+ def __init__(self, path, persistent=False):
+ super(Database.TestDBCreateIndexOnUpgrade, self).__init__(path, persistent, version="2")
+
+ def _db_upgrade_data_tables(self, old_version):
+ return self._db_execute(
+ """
+ create index TESTING on TESTTYPE (VALUE)
+ """
+ )
+
+ class TestDBPauseInInit(TestDB):
+
+ def _db_init(self):
+
+ time.sleep(1)
+ super(Database.TestDBPauseInInit, self)._db_init()
+
+ @inlineCallbacks
+ def inlineCallbackRaises(self, exc, f, *args, **kwargs):
+ try:
+ yield f(*args, **kwargs)
+ except exc:
+ pass
+ except Exception, e:
+ self.fail("Wrong exception raised: %s" % (e,))
+ else:
+ self.fail("%s not raised" % (exc,))
+
+ @inlineCallbacks
+ def test_connect(self):
+ """
+ Connect to database and create table
+ """
+ db = Database.TestDB(self.mktemp())
+ self.assertFalse(db.initialized)
+ yield db.open()
+ self.assertTrue(db.initialized)
+
+ @inlineCallbacks
+ def test_readwrite(self):
+ """
+ Add a record, search for it
+ """
+ db = Database.TestDB(self.mktemp())
+ yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", ("FOO", "BAR",))
+ items = (yield db.query("SELECT * from TESTTYPE"))
+ self.assertEqual(items, (("FOO", "BAR"),))
+ items = (yield db.queryList("SELECT * from TESTTYPE"))
+ self.assertEqual(items, ("FOO",))
+
+ @inlineCallbacks
+ def test_close(self):
+ """
+ Close database
+ """
+ db = Database.TestDB(self.mktemp())
+ self.assertFalse(db.initialized)
+ yield db.open()
+ db.close()
+ self.assertFalse(db.initialized)
+ db.close()
+
+ @inlineCallbacks
+ def test_version_upgrade_nonpersistent(self):
+ """
+ Connect to database and create table
+ """
+
+ db_file = self.mktemp()
+
+ db = Database.TestDB(db_file)
+ yield db.open()
+ yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", ("FOO", "BAR",))
+ items = (yield db.query("SELECT * from TESTTYPE"))
+ self.assertEqual(items, (("FOO", "BAR"),))
+ db.close()
+ db = None
+
+ db = Database.TestDBRecreateUpgrade(db_file)
+ yield self.inlineCallbackRaises(Database.TestDBRecreateUpgrade.RecreateDBException, db.open)
+ items = (yield db.query("SELECT * from TESTTYPE"))
+ self.assertEqual(items, ())
+
+ def test_version_upgrade_persistent(self):
+ """
+ Connect to database and create table
+ """
+ db_file = self.mktemp()
+ db = Database.TestDB(db_file, persistent=True)
+ yield db.open()
+ yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", "FOO", "BAR")
+ items = (yield db.query("SELECT * from TESTTYPE"))
+ self.assertEqual(items, (("FOO", "BAR")))
+ db.close()
+ db = None
+
+ db = Database.TestDBRecreateUpgrade(db_file, persistent=True)
+ yield self.inlineCallbackRaises(NotImplementedError, db.open)
+ self.assertTrue(os.path.exists(db_file))
+ db.close()
+ db = None
+
+ db = Database.TestDB(db_file, persistent=True, autocommit=True)
+ yield db.open()
+ items = (yield db.query("SELECT * from TESTTYPE"))
+ self.assertEqual(items, (("FOO", "BAR")))
+
+ def test_version_upgrade_persistent_add_index(self):
+ """
+ Connect to database and create table
+ """
+ db_file = self.mktemp()
+ db = Database.TestDB(db_file, persistent=True, autocommit=True)
+ yield db.open()
+ yield db.execute("INSERT into TESTTYPE (KEY, VALUE) values (:1, :2)", "FOO", "BAR")
+ items = (yield db.query("SELECT * from TESTTYPE"))
+ self.assertEqual(items, (("FOO", "BAR")))
+ db.close()
+ db = None
+
+ db = Database.TestDBCreateIndexOnUpgrade(db_file, persistent=True, autocommit=True)
+ yield db.open()
+ items = (yield db.query("SELECT * from TESTTYPE"))
+ self.assertEqual(items, (("FOO", "BAR")))
Modified: CalendarServer/trunk/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_upgrade.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/test/test_upgrade.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -41,7 +41,11 @@
"directory", "test", "accounts.xml")
config.DirectoryService.params.xmlFile = xmlFile
+ xmlAugmentsFile = os.path.join(os.path.dirname(os.path.dirname(__file__)),
+ "directory", "test", "augments.xml")
+ config.AugmentService.params.xmlFiles = (xmlAugmentsFile,)
+
def setUpInitialStates(self):
self.setUpXMLDirectory()
@@ -429,10 +433,10 @@
{
"@contents" : None,
},
- ResourceInfoDatabase.dbFilename :
- {
- "@contents" : None,
- }
+# ResourceInfoDatabase.dbFilename :
+# {
+# "@contents" : None,
+# }
}
self.verifyDirectoryComparison(before, after, reverify=True)
@@ -794,10 +798,10 @@
{
"@contents" : None,
},
- ResourceInfoDatabase.dbFilename :
- {
- "@contents" : None,
- }
+# ResourceInfoDatabase.dbFilename :
+# {
+# "@contents" : None,
+# }
}
self.verifyDirectoryComparison(before, after, reverify=True)
@@ -914,10 +918,10 @@
{
"@contents" : None,
},
- ResourceInfoDatabase.dbFilename :
- {
- "@contents" : None,
- }
+# ResourceInfoDatabase.dbFilename :
+# {
+# "@contents" : None,
+# }
}
self.verifyDirectoryComparison(before, after, reverify=True)
@@ -1033,10 +1037,10 @@
{
"@contents" : None,
},
- ResourceInfoDatabase.dbFilename :
- {
- "@contents" : None,
- }
+# ResourceInfoDatabase.dbFilename :
+# {
+# "@contents" : None,
+# }
}
self.verifyDirectoryComparison(before, after)
@@ -1125,10 +1129,10 @@
{
"@contents" : None,
},
- ResourceInfoDatabase.dbFilename :
- {
- "@contents" : None,
- }
+# ResourceInfoDatabase.dbFilename :
+# {
+# "@contents" : None,
+# }
}
@@ -1166,18 +1170,18 @@
{
"@contents" : "1",
},
- CalendarUserProxyDatabase.dbFilename :
- {
- "@contents" : None,
- },
+# CalendarUserProxyDatabase.dbFilename :
+# {
+# "@contents" : None,
+# },
MailGatewayTokensDatabase.dbFilename :
{
"@contents" : None,
},
- ResourceInfoDatabase.dbFilename :
- {
- "@contents" : None,
- }
+# ResourceInfoDatabase.dbFilename :
+# {
+# "@contents" : None,
+# }
}
root = self.createHierarchy(before)
config.DocumentRoot = root
@@ -1189,28 +1193,28 @@
calendarUserProxyDatabase = CalendarUserProxyDatabase(root)
resourceInfoDatabase = ResourceInfoDatabase(root)
- for guid, info in assignments.iteritems():
+# for guid, info in assignments.iteritems():
+#
+# proxyGroup = "%s#calendar-proxy-write" % (guid,)
+# result = set([row[0] for row in calendarUserProxyDatabase._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", proxyGroup)])
+# if info[1]:
+# self.assertTrue(info[1] in result)
+# else:
+# self.assertTrue(not result)
+#
+# readOnlyProxyGroup = "%s#calendar-proxy-read" % (guid,)
+# result = set([row[0] for row in calendarUserProxyDatabase._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", readOnlyProxyGroup)])
+# if info[2]:
+# self.assertTrue(info[2] in result)
+# else:
+# self.assertTrue(not result)
+#
+# autoSchedule = resourceInfoDatabase._db_value_for_sql("select AUTOSCHEDULE from RESOURCEINFO where GUID = :1", guid)
+# autoSchedule = autoSchedule == 1
+# self.assertEquals(info[0], autoSchedule)
- proxyGroup = "%s#calendar-proxy-write" % (guid,)
- result = set([row[0] for row in calendarUserProxyDatabase._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", proxyGroup)])
- if info[1]:
- self.assertTrue(info[1] in result)
- else:
- self.assertTrue(not result)
- readOnlyProxyGroup = "%s#calendar-proxy-read" % (guid,)
- result = set([row[0] for row in calendarUserProxyDatabase._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", readOnlyProxyGroup)])
- if info[2]:
- self.assertTrue(info[2] in result)
- else:
- self.assertTrue(not result)
- autoSchedule = resourceInfoDatabase._db_value_for_sql("select AUTOSCHEDULE from RESOURCEINFO where GUID = :1", guid)
- autoSchedule = autoSchedule == 1
- self.assertEquals(info[0], autoSchedule)
-
-
-
event01_before = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 3.0//EN
Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/test/util.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -42,8 +42,8 @@
dataroot = self.mktemp()
os.mkdir(dataroot)
config.DataRoot = dataroot
- config.Memcached.ClientEnabled = False
- config.Memcached.ServerEnabled = False
+ config.Memcached.Pools.Default.ClientEnabled = False
+ config.Memcached.Pools.Default.ServerEnabled = False
memcacheclient.ClientFactory.allowTestCache = True
memcacher.Memcacher.allowTestCache = True
Modified: CalendarServer/trunk/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/upgrade.py 2010-01-21 20:46:14 UTC (rev 4957)
+++ CalendarServer/trunk/twistedcaldav/upgrade.py 2010-01-25 15:56:33 UTC (rev 4958)
@@ -1,6 +1,6 @@
# -*- test-case-name: twistedcaldav.test.test_upgrade -*-
##
-# Copyright (c) 2008 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
from twisted.web2.dav.fileop import rmdir
from twisted.web2.dav import davxml
from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
from twistedcaldav.directory.resourceinfo import ResourceInfoDatabase
from twistedcaldav.mail import MailGatewayTokensDatabase
from twistedcaldav.log import Logger
@@ -208,44 +207,44 @@
def doProxyDatabaseMoveUpgrade(config, uid=-1, gid=-1):
+ pass
+# # See if the new one is already present
+# newDbPath = os.path.join(config.DataRoot,
+# CalendarUserProxyDatabase.dbFilename)
+# if os.path.exists(newDbPath):
+# # Nothing to be done, it's already in the new location
+# return
+#
+# # See if the old DB is present
+# oldDbPath = os.path.join(config.DocumentRoot, "principals",
+# CalendarUserProxyDatabase.dbOldFilename)
+# if not os.path.exists(oldDbPath):
+# # Nothing to be moved
+# return
+#
+# # Now move the old one to the new location
+# try:
+# if not os.path.exists(config.DataRoot):
+# makeDirsUserGroup(config.DataRoot, uid=uid, gid=gid)
+# try:
+# os.rename(oldDbPath, newDbPath)
+# except OSError:
+# # Can't rename, must copy/delete
+# shutil.copy2(oldDbPath, newDbPath)
+# os.remove(oldDbPath)
+#
+# except Exception, e:
+# raise UpgradeError(
+# "Upgrade Error: unable to move the old calendar user proxy database at '%s' to '%s' due to %s."
+# % (oldDbPath, newDbPath, str(e))
+# )
+#
+# log.debug(
+# "Moved the calendar user proxy database from '%s' to '%s'."
+# % (oldDbPath, newDbPath,)
+# )
- # See if the new one is already present
- newDbPath = os.path.join(config.DataRoot,
- CalendarUserProxyDatabase.dbFilename)
- if os.path.exists(newDbPath):
- # Nothing to be done, it's already in the new location
- return
- # See if the old DB is present
- oldDbPath = os.path.join(config.DocumentRoot, "principals",
- CalendarUserProxyDatabase.dbOldFilename)
- if not os.path.exists(oldDbPath):
- # Nothing to be moved
- return
-
- # Now move the old one to the new location
- try:
- if not os.path.exists(config.DataRoot):
- makeDirsUserGroup(config.DataRoot, uid=uid, gid=gid)
- try:
- os.rename(oldDbPath, newDbPath)
- except OSError:
- # Can't rename, must copy/delete
- shutil.copy2(oldDbPath, newDbPath)
- os.remove(oldDbPath)
-
- except Exception, e:
- raise UpgradeError(
- "Upgrade Error: unable to move the old calendar user proxy database at '%s' to '%s' due to %s."
- % (oldDbPath, newDbPath, str(e))
- )
-
- log.debug(
- "Moved the calendar user proxy database from '%s' to '%s'."
- % (oldDbPath, newDbPath,)
- )
-
-
def moveCalendarHome(oldHome, newHome, uid=-1, gid=-1):
if os.path.exists(newHome):
# Both old and new homes exist; stop immediately to let the
@@ -261,31 +260,35 @@
def migrateResourceInfo(config, directory, uid, gid):
- log.info("Fetching delegate assignments and auto-schedule settings from directory")
- resourceInfoDatabase = ResourceInfoDatabase(config.DataRoot)
- calendarUserProxyDatabase = CalendarUserProxyDatabase(config.DataRoot)
- resourceInfo = directory.getResourceInfo()
- for guid, autoSchedule, proxy, readOnlyProxy in resourceInfo:
- resourceInfoDatabase.setAutoScheduleInDatabase(guid, autoSchedule)
- if proxy:
- calendarUserProxyDatabase.setGroupMembersInDatabase(
- "%s#calendar-proxy-write" % (guid,),
- [proxy]
- )
- if readOnlyProxy:
- calendarUserProxyDatabase.setGroupMembersInDatabase(
- "%s#calendar-proxy-read" % (guid,),
- [readOnlyProxy]
- )
+ # TODO: we need to account for the new augments database. This means migrating from the pre-resource info
+ # implementation and the resource-info implementation
+ pass
- dbPath = os.path.join(config.DataRoot, ResourceInfoDatabase.dbFilename)
- if os.path.exists(dbPath):
- os.chown(dbPath, uid, gid)
+# log.info("Fetching delegate assignments and auto-schedule settings from directory")
+# resourceInfoDatabase = ResourceInfoDatabase(config.DataRoot)
+# calendarUserProxyDatabase = CalendarUserProxyDatabase(config.DataRoot)
+# resourceInfo = directory.getResourceInfo()
+# for guid, autoSchedule, proxy, readOnlyProxy in resourceInfo:
+# resourceInfoDatabase.setAutoScheduleInDatabase(guid, autoSchedule)
+# if proxy:
+# calendarUserProxyDatabase.setGroupMembersInDatabase(
+# "%s#calendar-proxy-write" % (guid,),
+# [proxy]
+# )
+# if readOnlyProxy:
+# calendarUserProxyDatabase.setGroupMembersInDatabase(
+# "%s#calendar-proxy-read" % (guid,),
+# [readOnlyProxy]
+# )
+#
+# dbPath = os.path.join(config.DataRoot, ResourceInfoDatabase.dbFilename)
+# if os.path.exists(dbPath):
+# os.chown(dbPath, uid, gid)
+#
+# dbPath = os.path.join(config.DataRoot, CalendarUserProxyDatabase.dbFilename)
+# if os.path.exists(dbPath):
+# os.chown(dbPath, uid, gid)
- dbPath = os.path.join(config.DataRoot, CalendarUserProxyDatabase.dbFilename)
- if os.path.exists(dbPath):
- os.chown(dbPath, uid, gid)
-
def createMailTokensDatabase(config, uid, gid):
# Cause the tokens db to be created on disk so we can set the
# permissions on it now
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100125/fda01adc/attachment-0001.html>
More information about the calendarserver-changes
mailing list