[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