[CalendarServer-changes] [10642] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Mon Feb 4 08:59:07 PST 2013
Revision: 10642
http://trac.calendarserver.org//changeset/10642
Author: cdaboo at apple.com
Date: 2013-02-04 08:59:07 -0800 (Mon, 04 Feb 2013)
Log Message:
-----------
Don't allow (new) duplicate UIDs to be stored in the same calendar home. Remove duplicates when scheduling to
allow scheduling to only target a single resource.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tools/calverify.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
CalendarServer/trunk/twistedcaldav/scheduling/utils.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/sql.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/scheduling/test/accounts.xml
CalendarServer/trunk/twistedcaldav/scheduling/test/resources.xml
CalendarServer/trunk/twistedcaldav/scheduling/test/test_utils.py
Modified: CalendarServer/trunk/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/calverify.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/calendarserver/tools/calverify.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -1659,7 +1659,7 @@
calendar = yield home.childWithID(calendarID)
calendarObj = yield calendar.objectResourceWithID(resid)
objname = calendarObj.name()
- yield calendar._removeObjectResource(calendarObj)
+ yield calendar.removeObjectResource(calendarObj)
yield self.txn.commit()
self.txn = self.store.newTransaction()
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -912,6 +912,39 @@
@inlineCallbacks
+ def hasCalendarResourceUIDSomewhereElse(self, uid):
+ """
+ See if a calendar component with a matching UID exists anywhere in the calendar home of the
+ current recipient owner and is not the resource being targeted.
+ """
+
+ # Ignore for an overwrite or a MOVE
+ if self.destination.exists() or self.sourceparent and self.deletesource:
+ returnValue(None)
+
+ failed = False
+
+ # Always fail a copy
+ if self.sourceparent and self.sourcecal and not self.deletesource and self.destinationcal:
+ failed = True
+ else:
+ # Get owner's calendar-home
+ calendar_owner_principal = (yield self.destination.resourceOwnerPrincipal(self.request))
+ calendar_home = yield calendar_owner_principal.calendarHome(self.request)
+
+ # Check for matching resource somewhere else in the home use the "schedule" mode to prevent any kind of match
+ failed = (yield calendar_home.hasCalendarResourceUIDSomewhereElse(uid, self.destination, "schedule"))
+
+ if failed:
+ log.debug("Implicit - found component with same UID in a different collection: %s" % (self.destination_uri,))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "unique-scheduling-object-resource"),
+ "Cannot duplicate scheduling object resource",
+ ))
+
+
+ @inlineCallbacks
def doImplicitScheduling(self):
data_changed = False
@@ -1179,6 +1212,7 @@
),
"UID already exists",
))
+ yield self.hasCalendarResourceUIDSomewhereElse(self.uid)
# Preserve private comments
yield self.preservePrivateComments()
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -2662,13 +2662,13 @@
returnValue(similar)
- def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, type):
+ def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, mode):
"""
Test if there are other child object resources with the specified UID.
Pass through direct to store.
"""
- return self._newStoreHome.hasCalendarResourceUIDSomewhereElse(uid, ok_object._newStoreObject, type)
+ return self._newStoreHome.hasCalendarResourceUIDSomewhereElse(uid, ok_object._newStoreObject, mode)
def getCalendarResourcesForUID(self, uid, allow_shared=False):
Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -452,7 +452,7 @@
@inlineCallbacks
- def hasCalendarResourceUIDSomewhereElse(self, check_resource, check_uri, type):
+ def hasCalendarResourceUIDSomewhereElse(self, check_resource, check_uri, mode):
"""
See if a calendar component with a matching UID exists anywhere in the calendar home of the
current recipient owner and is not the resource being targeted.
@@ -467,7 +467,7 @@
calendar_home = yield calendar_owner_principal.calendarHome(self.request)
# Check for matching resource somewhere else in the home
- foundElsewhere = (yield calendar_home.hasCalendarResourceUIDSomewhereElse(self.uid, check_resource, type))
+ foundElsewhere = (yield calendar_home.hasCalendarResourceUIDSomewhereElse(self.uid, check_resource, mode))
if foundElsewhere:
log.debug("Implicit - found component with same UID in a different collection: %s" % (check_uri,))
raise HTTPError(ErrorResponse(
Added: CalendarServer/trunk/twistedcaldav/scheduling/test/accounts.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/accounts.xml (rev 0)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/accounts.xml 2013-02-04 16:59:07 UTC (rev 10642)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2013 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/accounts.dtd">
+
+<accounts realm="Test">
+ <user repeat="99">
+ <uid>user%02d</uid>
+ <uid>User %02d</uid>
+ <guid>user%02d</guid>
+ <password>user%02d</password>
+ <name>User %02d</name>
+ <first-name>User</first-name>
+ <last-name>%02d</last-name>
+ <email-address>user%02d at example.com</email-address>
+ </user>
+</accounts>
Added: CalendarServer/trunk/twistedcaldav/scheduling/test/resources.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/resources.xml (rev 0)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/resources.xml 2013-02-04 16:59:07 UTC (rev 10642)
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<accounts realm="/Search">
+</accounts>
Property changes on: CalendarServer/trunk/twistedcaldav/scheduling/test/resources.xml
___________________________________________________________________
Added: svn:executable
+ *
Added: CalendarServer/trunk/twistedcaldav/scheduling/test/test_utils.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_utils.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_utils.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -0,0 +1,200 @@
+##
+# Copyright (c) 2013 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.scheduling.utils import getCalendarObjectForPrincipals
+
+"""
+Tests for calendarserver.tools.purge
+"""
+
+from calendarserver.tap.util import getRootResource, FakeRequest
+
+from pycalendar.datetime import PyCalendarDateTime
+
+from twisted.internet.defer import inlineCallbacks
+from twisted.trial import unittest
+
+from twistedcaldav.config import config
+
+from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
+
+import os
+
+
+now = PyCalendarDateTime.getToday().getYear()
+
+ORGANIZER_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:685BC3A1-195A-49B3-926D-388DDACA78A6
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:%(year)s0307T111500Z
+DURATION:PT1H
+DTSTAMP:20100303T181220Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user02
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n") % {"year": now + 1}
+
+ATTENDEE_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:685BC3A1-195A-49B3-926D-388DDACA78A6
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:%(year)s0307T111500Z
+DURATION:PT1H
+DTSTAMP:20100303T181220Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user02
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n") % {"year": now + 1}
+
+
+
+class RecipientCopy(CommonCommonTests, unittest.TestCase):
+ """
+ Tests for deleting events older than a given date
+ """
+
+ metadata = {
+ "accessMode": "PUBLIC",
+ "isScheduleObject": True,
+ "scheduleTag": "abc",
+ "scheduleEtags": (),
+ "hasPrivateComment": False,
+ }
+
+ requirements = {
+ "user01" : {
+ "calendar1" : {
+ "1.ics" : (ORGANIZER_ICS, metadata,),
+ }
+ },
+ "user02" : {
+ "calendar1" : {
+ "2.ics" : (ATTENDEE_ICS, metadata,),
+ },
+ "calendar3" : {
+ "3.ics" : (ATTENDEE_ICS, metadata,),
+ }
+ }
+ }
+
+ @inlineCallbacks
+ def setUp(self):
+
+ yield super(RecipientCopy, self).setUp()
+ self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
+ yield self.populate()
+
+ self.patch(config.DirectoryService.params, "xmlFile",
+ os.path.join(
+ os.path.dirname(__file__), "accounts.xml"
+ )
+ )
+ self.patch(config.ResourceService.params, "xmlFile",
+ os.path.join(
+ os.path.dirname(__file__), "resources.xml"
+ )
+ )
+ self.rootResource = getRootResource(config, self._sqlCalendarStore)
+ self.directory = self.rootResource.getDirectory()
+
+
+ @inlineCallbacks
+ def populate(self):
+ yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+ self.notifierFactory.reset()
+
+
+ def storeUnderTest(self):
+ """
+ Create and return a L{CalendarStore} for testing.
+ """
+ return self._sqlCalendarStore
+
+
+ @inlineCallbacks
+ def test_getCalendarObjectForPrincipals(self):
+ """
+ Test that L{twistedcaldav.scheduling.utils.getCalendarObjectForPrincipals} detects and removes
+ resources with duplicate UIDs in the same calendar home.
+ """
+
+ # Check that expected resources are present
+ request = FakeRequest(self.rootResource, "PUT", path='/user01/outbox')
+ for uri in (
+ "/calendars/__uids__/user01/calendar1/1.ics",
+ "/calendars/__uids__/user02/calendar1/2.ics",
+ "/calendars/__uids__/user02/calendar2/3.ics",
+ ):
+ resource = (yield request.locateResource(uri))
+ self.assertNotEqual(resource, None)
+ yield request._newStoreTransaction.commit()
+
+ # Look up resource by UID in home where only one exists
+ request = FakeRequest(self.rootResource, "PUT", path='/user01/outbox')
+ principalCollection = self.directory.principalCollection
+ principal = principalCollection.principalForUID("user01")
+ _ignore_resource, rname, _ignore_calendar, calendar_uri = (yield getCalendarObjectForPrincipals(request, principal, "685BC3A1-195A-49B3-926D-388DDACA78A6"))
+ self.assertEqual(rname, "1.ics")
+ self.assertEqual(calendar_uri, "/calendars/__uids__/user01/calendar1")
+ yield request._newStoreTransaction.commit()
+
+ # Check that expected resources are still present
+ request = FakeRequest(self.rootResource, "PUT", path='/user01/outbox')
+ for uri in (
+ "/calendars/__uids__/user01/calendar1/1.ics",
+ "/calendars/__uids__/user02/calendar1/2.ics",
+ "/calendars/__uids__/user02/calendar2/3.ics",
+ ):
+ resource = (yield request.locateResource(uri))
+ self.assertNotEqual(resource, None)
+
+ # Look up resource by UID in home where two exists
+ request = FakeRequest(self.rootResource, "PUT", path='/user01/outbox')
+ principalCollection = self.directory.principalCollection
+ principal = principalCollection.principalForUID("user02")
+ _ignore_resource, rname, _ignore_calendar, calendar_uri = (yield getCalendarObjectForPrincipals(request, principal, "685BC3A1-195A-49B3-926D-388DDACA78A6"))
+ self.assertTrue(
+ (rname, calendar_uri) in
+ (
+ ("2.ics", "/calendars/__uids__/user02/calendar2"),
+ ("3.ics", "/calendars/__uids__/user02/calendar3"),
+ )
+ )
+ yield request._newStoreTransaction.commit()
+
+ # Check that expected resources are still present, but the duplicate missing
+ request = FakeRequest(self.rootResource, "PUT", path='/user01/outbox')
+ resource = (yield request.locateResource("/calendars/__uids__/user01/calendar1/1.ics"))
+ self.assertNotEqual(resource, None)
+ resource2 = (yield request.locateResource("/calendars/__uids__/user02/calendar2/2.ics"))
+ resource3 = (yield request.locateResource("/calendars/__uids__/user02/calendar3/3.ics"))
+ self.assertTrue(resource2.exists() ^ resource3.exists())
Modified: CalendarServer/trunk/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/twistedcaldav/scheduling/utils.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -24,6 +24,9 @@
def getCalendarObjectForPrincipals(request, principal, uid, allow_shared=False):
"""
Get a copy of the event for a principal.
+
+ NOTE: if more than one resource with the same UID is found, we will delete all but
+ one of them to avoid scheduling problems.
"""
result = {
@@ -44,13 +47,18 @@
# Get matching newstore objects
objectResources = (yield calendar_home.getCalendarResourcesForUID(uid, allow_shared))
+ if len(objectResources) > 1:
+ # Delete all but the first one
+ log.debug("Should only have zero or one scheduling object resource with UID '%s' in calendar home: %s" % (uid, calendar_home,))
+ for resource in objectResources[1:]:
+ yield resource._parentCollection.removeObjectResource(resource)
+ objectResources = objectResources[:1]
+
# We really want only one or zero of these
if len(objectResources) == 1:
result["calendar_collection_uri"] = joinURL(calendar_home.url(), objectResources[0]._parentCollection.name())
result["calendar_collection"] = (yield request.locateResource(result["calendar_collection_uri"]))
result["resource_name"] = objectResources[0].name()
result["resource"] = (yield request.locateResource(joinURL(result["calendar_collection_uri"], result["resource_name"])))
- elif len(objectResources):
- log.debug("Should only have zero or one scheduling object resource with UID '%s' in calendar home: %s" % (uid, calendar_home,))
returnValue((result["resource"], result["resource_name"], result["calendar_collection"], result["calendar_collection_uri"],))
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -2542,7 +2542,7 @@
@type request: L{twext.web2.iweb.IRequest}
@param implicitly: Should implicit scheduling operations be triggered
- as a resut of this C{DELETE}?
+ as a result of this C{DELETE}?
@type implicitly: C{bool}
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -390,7 +390,7 @@
Otherwise, (if this is the string "calendar") we are checking for
conflicts with a new unscheduled calendar object, which will
conflict only with other scheduled objects.
- @type type: C{str}
+ @type mode: C{str}
@return: a L{Deferred} which fires with C{True} if there is a conflict
and C{False} if not.
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2013-02-04 16:50:24 UTC (rev 10641)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2013-02-04 16:59:07 UTC (rev 10642)
@@ -3493,7 +3493,7 @@
child = (yield self.objectResourceWithName(name))
if child is None:
raise NoSuchObjectResourceError
- yield self._removeObjectResource(child)
+ yield self.removeObjectResource(child)
@inlineCallbacks
@@ -3502,11 +3502,11 @@
child = (yield self.objectResourceWithUID(uid))
if child is None:
raise NoSuchObjectResourceError
- yield self._removeObjectResource(child)
+ yield self.removeObjectResource(child)
@inlineCallbacks
- def _removeObjectResource(self, child):
+ def removeObjectResource(self, child):
name = child.name()
uid = child.uid()
yield child.remove()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130204/b556a7f9/attachment-0001.html>
More information about the calendarserver-changes
mailing list