[CalendarServer-changes] [10973] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Tue Apr 2 11:59:24 PDT 2013
Revision: 10973
http://trac.calendarserver.org//changeset/10973
Author: cdaboo at apple.com
Date: 2013-04-02 11:59:24 -0700 (Tue, 02 Apr 2013)
Log Message:
-----------
Add X- property is for unscheduled events created on location or resource calendars. Prevent unscheduled events on
location or resource calendars if so configured.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/twistedcaldav/test/test_validation.py
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2013-04-02 18:56:24 UTC (rev 10972)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2013-04-02 18:59:24 UTC (rev 10973)
@@ -203,6 +203,7 @@
self.access = None
self.hasPrivateComments = False
self.isScheduleResource = False
+ self.dataChanged = False
@inlineCallbacks
@@ -365,6 +366,9 @@
# Check that moves to shared calendars are OK
yield self.validCopyMoveOperation()
+ # Check location/resource organizer requirement
+ yield self.validLocationResourceOrganizer()
+
# Check access
if self.destinationcal and config.EnablePrivateEvents:
result = (yield self.validAccess())
@@ -662,6 +666,53 @@
@inlineCallbacks
+ def validLocationResourceOrganizer(self):
+ """
+ If the calendar owner is a location or resource, check whether an ORGANIZER property is required.
+ """
+
+ if not self.internal_request:
+ originatorPrincipal = (yield self.destination.ownerPrincipal(self.request))
+ cutype = originatorPrincipal.getCUType() if originatorPrincipal is not None else "INDIVIDUAL"
+ organizer = self.calendar.getOrganizer()
+
+ # Check for an allowed change
+ if organizer is None and (
+ cutype == "ROOM" and not config.Scheduling.Options.AllowLocationWithoutOrganizer or
+ cutype == "RESOURCE" and not config.Scheduling.Options.AllowResourceWithoutOrganizer):
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "valid-organizer"),
+ "Organizer required in calendar data",
+ ))
+
+ # Check for tracking the modifier
+ if organizer is None and (
+ cutype == "ROOM" and config.Scheduling.Options.TrackUnscheduledLocationData or
+ cutype == "RESOURCE" and config.Scheduling.Options.TrackUnscheduledResourceData):
+
+ # Find current principal
+ authz = None
+ authz_principal = self.destinationparent.currentPrincipal(self.request).children[0]
+ if isinstance(authz_principal, davxml.HRef):
+ principalURL = str(authz_principal)
+ if principalURL:
+ authz = (yield self.request.locateResource(principalURL))
+
+ if authz is not None:
+ prop = Property("X-CALENDARSERVER-MODIFIED-BY", "urn:uuid:%s" % (authz.record.guid,))
+ prop.setParameter("CN", authz.displayName())
+ for candidate in authz.calendarUserAddresses():
+ if candidate.startswith("mailto:"):
+ prop.setParameter("EMAIL", candidate)
+ break
+ self.calendar.replacePropertyInAllComponents(prop)
+ else:
+ self.calendar.removeAllPropertiesWithName("X-CALENDARSERVER-MODIFIED-BY")
+ self.dataChanged = True
+
+
+ @inlineCallbacks
def preservePrivateComments(self):
# Check for private comments on the old resource and the new resource and re-insert
# ones that are lost.
@@ -769,7 +820,6 @@
"""
# Only relevant if calendar is sharee collection
- changed = False
if self.destinationparent.isShareeCollection():
# Get all X-APPLE-DROPBOX's and ATTACH's that are http URIs
@@ -813,59 +863,52 @@
uri = uriNormalize(xdropbox.value())
if uri:
xdropbox.setValue(uri)
- changed = True
+ self.dataChanged = True
for attachment in attachments:
uri = uriNormalize(attachment.value())
if uri:
attachment.setValue(uri)
- changed = True
+ self.dataChanged = True
- returnValue(changed)
-
def processAlarms(self):
"""
Remove duplicate alarms. Add a default alarm if required.
-
- @return: indicate whether a change was made
- @rtype: C{bool}
"""
# Remove duplicate alarms
- changed = False
- if config.RemoveDuplicateAlarms:
- changed = self.calendar.hasDuplicateAlarms(doFix=True)
+ if config.RemoveDuplicateAlarms and self.calendar.hasDuplicateAlarms(doFix=True):
+ self.dataChanged = True
# Only if feature enabled
if not config.EnableDefaultAlarms:
- return changed
+ return
# Check that we are creating and this is not the inbox
if not self.destinationcal or self.destination.exists() or self.isiTIP:
- return changed
+ return
# Never add default alarms to calendar data in shared calendars
if self.destinationparent.isShareeCollection():
- return changed
+ return
# Add default alarm for VEVENT and VTODO only
mtype = self.calendar.mainType().upper()
if self.calendar.mainType().upper() not in ("VEVENT", "VTODO"):
- return changed
+ return
vevent = mtype == "VEVENT"
# Check timed or all-day
start, _ignore_end = self.calendar.mainComponent(allow_multiple=True).getEffectiveStartEnd()
if start is None:
# Yes VTODOs might have no DTSTART or DUE - in this case we do not add a default
- return changed
+ return
timed = not start.isDateOnly()
# See if default exists and add using appropriate logic
alarm = self.destinationparent.getDefaultAlarm(vevent, timed)
- if alarm:
- changed = self.calendar.addAlarms(alarm)
- return changed
+ if alarm and self.calendar.addAlarms(alarm):
+ self.dataChanged = True
@inlineCallbacks
@@ -1221,14 +1264,14 @@
yield self.replaceMissingToDoProperties()
# Handle sharing dropbox normalization
- dropboxChanged = (yield self.dropboxPathNormalization())
+ yield self.dropboxPathNormalization()
# Pre-process managed attachments
if not self.internal_request and not self.attachmentProcessingDone:
managed_copied, managed_removed = (yield self.destination.preProcessManagedAttachments(self.calendar))
# Default/duplicate alarms
- alarmChanged = self.processAlarms()
+ self.processAlarms()
# Do scheduling
implicit_result = (yield self.doImplicitScheduling())
@@ -1276,7 +1319,7 @@
yield self.destination.postProcessManagedAttachments(managed_copied, managed_removed)
# Must not set ETag in response if data changed
- if did_implicit_action or dropboxChanged or alarmChanged:
+ if did_implicit_action or self.dataChanged:
def _removeEtag(request, response):
response.headers.removeHeader('etag')
return response
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-04-02 18:56:24 UTC (rev 10972)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-04-02 18:59:24 UTC (rev 10973)
@@ -694,6 +694,10 @@
"AllowGroupAsOrganizer" : False, # Allow groups to be Organizers
"AllowLocationAsOrganizer" : False, # Allow locations to be Organizers
"AllowResourceAsOrganizer" : False, # Allow resources to be Organizers
+ "AllowLocationWithoutOrganizer" : True, # Allow locations to have events without an Organizer
+ "AllowResourceWithoutOrganizer" : True, # Allow resources to have events without an Organizer
+ "TrackUnscheduledLocationData" : True, # Track who the last modifier of an unscheduled location event is
+ "TrackUnscheduledResourceData" : True, # Track who the last modifier of an unscheduled resource event is
"LimitFreeBusyAttendees" : 30, # Maximum number of attendees to request freebusy for
"AttendeeRefreshBatch" : 5, # Number of attendees to do batched refreshes: 0 - no batching
"AttendeeRefreshBatchDelaySeconds" : 5, # Time after an iTIP REPLY for first batched attendee refresh
@@ -1035,6 +1039,7 @@
return configDict
+
def _expandPath(path):
if '$' in path:
return path.replace('$', getfqdn())
Modified: CalendarServer/trunk/twistedcaldav/test/test_validation.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_validation.py 2013-04-02 18:56:24 UTC (rev 10972)
+++ CalendarServer/trunk/twistedcaldav/test/test_validation.py 2013-04-02 18:59:24 UTC (rev 10973)
@@ -23,6 +23,7 @@
# to address this to use system twisted.
from twext.web2.test.test_server import SimpleRequest
from twext.web2.http import HTTPError
+from twext.web2.resource import Resource
from twistedcaldav.config import config
from twistedcaldav.ical import Component, Property
@@ -31,16 +32,20 @@
from twistedcaldav.resource import CalDAVResource
class InMemoryCalendarObjectResource(CalDAVResource):
-
+
def exists(self):
return hasattr(self, "_data") and self._data is not None
+
def iCalendarForUser(self, user):
return self._data
-
+
+
def setData(self, data):
self._data = data
+
+
class TestCopyMoveValidation(TestCase):
"""
Tests for the validation code in L{twistedcaldav.method.put_common}.
@@ -52,11 +57,12 @@
"""
self.destination = InMemoryCalendarObjectResource()
- self.destination.name = lambda : '1'
+ self.destination.name = lambda : 'bar'
self.destinationParent = CalDAVResource()
- self.destinationParent.name = lambda : '2'
+ self.destinationParent.name = lambda : 'foo'
self.destinationParent.isSupportedComponent = lambda x: True
+
def _getSampleCalendar(self):
return Component.fromString("""BEGIN:VCALENDAR
VERSION:2.0
@@ -72,9 +78,13 @@
END:VCALENDAR
""")
+
def _getStorer(self, calendar):
self.sampleCalendar = calendar
req = SimpleRequest(None, "COPY", "http://example.com/foo/bar")
+ req._rememberResource(self.destination, "/foo/bar")
+ req._rememberResource(self.destinationParent, "/foo/")
+ req._rememberResource(Resource(), "/")
self.storer = StoreCalendarObjectResource(
req,
destination=self.destination,
@@ -83,7 +93,8 @@
calendar=self.sampleCalendar
)
return self.storer
-
+
+
@inlineCallbacks
def test_simpleValidRequest(self):
"""
@@ -110,7 +121,7 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
@@ -143,7 +154,7 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance - 5):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
@@ -155,7 +166,7 @@
config.MaxAttendeesPerInstance -= 10
eventComponent.addProperty(
Property("ATTENDEE", "mailto:user-extra at example.com"))
-
+
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
except HTTPError, err:
@@ -187,7 +198,7 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance - 5):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
@@ -197,7 +208,7 @@
# Now reduce the limit and try to store without any additional attendees.
config.MaxAttendeesPerInstance -= 10
-
+
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
except HTTPError:
@@ -209,8 +220,8 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance + 2):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
-
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
+
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
except HTTPError:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130402/d670be5d/attachment-0001.html>
More information about the calendarserver-changes
mailing list