[CalendarServer-changes] [9821] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Wed Sep 19 07:59:42 PDT 2012
Revision: 9821
http://trac.calendarserver.org//changeset/9821
Author: cdaboo at apple.com
Date: 2012-09-19 07:59:42 -0700 (Wed, 19 Sep 2012)
Log Message:
-----------
Upgrade/downgrade of shared collection now only happens when the sharee list changes - MKxxx and PROPPATCH no longer allowed.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/method/mkcol.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/test/test_sharing.py
Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py 2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py 2012-09-19 14:59:42 UTC (rev 9821)
@@ -36,7 +36,7 @@
from twistedcaldav import caldavxml, carddavxml, mkcolxml
from twistedcaldav.config import config
-from twistedcaldav.resource import isAddressBookCollectionResource,\
+from twistedcaldav.resource import isAddressBookCollectionResource, \
CalDAVResource
from twistedcaldav.resource import isPseudoCalendarCollectionResource
@@ -77,7 +77,7 @@
if config.EnableCalDAV:
parent = (yield self._checkParents(request, isPseudoCalendarCollectionResource))
-
+
if parent is not None:
raise HTTPError(StatusResponse(
responsecode.FORBIDDEN,
@@ -86,7 +86,7 @@
if config.EnableCardDAV:
parent = (yield self._checkParents(request, isAddressBookCollectionResource))
-
+
if parent is not None:
raise HTTPError(StatusResponse(
responsecode.FORBIDDEN,
@@ -123,7 +123,7 @@
errors = PropertyStatusResponseQueue("PROPPATCH", request.uri, responsecode.NO_CONTENT)
got_an_error = False
-
+
set_supported_component_set = False
if mkcol.children:
# mkcol -> set -> prop -> property*
@@ -143,6 +143,7 @@
rtype = "calendar"
elif property.childrenOfType(carddavxml.AddressBook):
rtype = "addressbook"
+
if not rtype:
error = "No {DAV:}resourcetype property in MKCOL request body: %s" % (mkcol,)
log.err(error)
@@ -151,14 +152,14 @@
error = "{DAV:}resourcetype property in MKCOL request body not supported: %s" % (mkcol,)
log.err(error)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-
+
# Make sure feature is enabled
if (rtype == "calendar" and not config.EnableCalDAV or
rtype == "addressbook" and not config.EnableCardDAV):
error = "{DAV:}resourcetype property in MKCOL request body not supported: %s" % (mkcol,)
log.err(error)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-
+
# Now create the special collection
if rtype == "calendar":
yield self.createCalendar(request)
@@ -166,19 +167,19 @@
yield self.createAddressBook(request)
# Now handle other properties
- for property in mkcol.children[0].children[0].children:
+ for property in properties:
try:
if rtype == "calendar" and property.qname() == (caldavxml.caldav_namespace, "supported-calendar-component-set"):
yield self.setSupportedComponentSet(property)
set_supported_component_set = True
- else:
+ elif not isinstance(property, davxml.ResourceType):
yield self.writeProperty(property, request)
except HTTPError:
errors.add(Failure(), property)
got_an_error = True
else:
errors.add(responsecode.OK, property)
-
+
if got_an_error:
# Clean up
self.transactionError()
@@ -187,15 +188,14 @@
code=responsecode.FORBIDDEN,
stream=mkcolxml.MakeCollectionResponse(errors.response()).toxml()
))
-
+
# When calendar collections are single component only, default MKCALENDAR is VEVENT only
if rtype == "calendar" and not set_supported_component_set and config.RestrictCalendarsToOneComponentType:
yield self.setSupportedComponents(("VEVENT",))
yield returnValue(responsecode.CREATED)
-
+
else:
# No request body so it is a standard MKCOL
result = yield super(CalDAVResource, self).http_MKCOL(request)
returnValue(result)
-
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2012-09-19 14:59:42 UTC (rev 9821)
@@ -48,7 +48,7 @@
from twext.web2 import responsecode, http, http_headers
from twext.web2.dav.auth import AuthenticationWrapper as SuperAuthenticationWrapper
from twext.web2.dav.idav import IDAVPrincipalCollectionResource
-from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource,\
+from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource, \
davPrivilegeSet
from twext.web2.dav.resource import TwistedACLInheritable
from twext.web2.dav.util import joinURL, parentForURL, normalizeURL
@@ -59,7 +59,7 @@
from twistedcaldav import caldavxml, customxml
from twistedcaldav import carddavxml
-from twistedcaldav.cache import PropfindCacheMixin, DisabledCacheNotifier,\
+from twistedcaldav.cache import PropfindCacheMixin, DisabledCacheNotifier, \
CacheStoreNotifier
from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.carddavxml import carddav_namespace
@@ -69,7 +69,7 @@
from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
from twistedcaldav.datafilters.privateevents import PrivateEventFilter
from twistedcaldav.directory.internal import InternalDirectoryRecord
-from twistedcaldav.extensions import DAVResource, DAVPrincipalResource,\
+from twistedcaldav.extensions import DAVResource, DAVPrincipalResource, \
PropertyNotFoundError, DAVResourceWithChildrenMixin
from twistedcaldav.ical import Component
@@ -113,6 +113,8 @@
+ config.CalDAVComplianceClasses
)
+
+
class ReadOnlyResourceMixIn (object):
"""
Read only resource.
@@ -121,13 +123,31 @@
def writeProperty(self, property, request):
raise HTTPError(self.readOnlyResponse)
- def http_ACL(self, request): return responsecode.FORBIDDEN
- def http_DELETE(self, request): return responsecode.FORBIDDEN
- def http_MKCOL(self, request): return responsecode.FORBIDDEN
- def http_MOVE(self, request): return responsecode.FORBIDDEN
- def http_PROPPATCH(self, request): return responsecode.FORBIDDEN
- def http_PUT(self, request): return responsecode.FORBIDDEN
+ def http_ACL(self, request):
+ return responsecode.FORBIDDEN
+
+
+ def http_DELETE(self, request):
+ return responsecode.FORBIDDEN
+
+
+ def http_MKCOL(self, request):
+ return responsecode.FORBIDDEN
+
+
+ def http_MOVE(self, request):
+ return responsecode.FORBIDDEN
+
+
+ def http_PROPPATCH(self, request):
+ return responsecode.FORBIDDEN
+
+
+ def http_PUT(self, request):
+ return responsecode.FORBIDDEN
+
+
def http_MKCALENDAR(self, request):
return ErrorResponse(
responsecode.FORBIDDEN,
@@ -135,14 +155,19 @@
"Resource is read-only",
)
+
+
class ReadOnlyNoCopyResourceMixIn (ReadOnlyResourceMixIn):
"""
Read only resource that disallows COPY.
"""
- def http_COPY(self, request): return responsecode.FORBIDDEN
+ def http_COPY(self, request):
+ return responsecode.FORBIDDEN
-def _calendarPrivilegeSet ():
+
+
+def _calendarPrivilegeSet():
edited = False
top_supported_privileges = []
@@ -197,6 +222,7 @@
return fun
+
class CalDAVResource (
CalDAVComplianceMixIn, SharedCollectionMixin,
DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn
@@ -244,7 +270,7 @@
# Render a monolithic iCalendar file
if request.path[-1] != "/":
# Redirect to include trailing '/' in URI
- return RedirectResponse(request.unparseURL(path=urllib.quote(urllib.unquote(request.path), safe=':/')+'/'))
+ return RedirectResponse(request.unparseURL(path=urllib.quote(urllib.unquote(request.path), safe=':/') + '/'))
def _defer(data):
response = Response()
@@ -258,7 +284,6 @@
return super(CalDAVResource, self).render(request)
-
_associatedTransaction = None
_transactionError = False
@@ -271,7 +296,7 @@
@param transaction: the transaction to associate this resource and its
children with.
- @type transaction: L{txdav.caldav.idav.ITransaction}
+ @type transaction: L{txdav.caldav.idav.ITransaction}
"""
# FIXME: needs to reject association with transaction if it's already
# got one (resources associated with a transaction are not reusable)
@@ -339,8 +364,8 @@
"""
raise NotImplementedError("%s does not implement newStoreProperties" %
(self,))
-
-
+
+
def storeRemove(self, *a, **kw):
"""
Remove this resource from storage.
@@ -355,36 +380,40 @@
@param stream: The stream containing the data to be stored.
@type stream: L{IStream}
-
+
@return: a L{Deferred} which fires with an HTTP response.
@rtype: L{Deferred}
"""
- raise NotImplementedError("%s does not implement storeStream" %
+ raise NotImplementedError("%s does not implement storeStream" %
(self,))
- # End transitional new-store interface
+ # End transitional new-store interface
+
@updateCacheTokenOnCallback
def http_PROPPATCH(self, request):
return super(CalDAVResource, self).http_PROPPATCH(request)
+
@updateCacheTokenOnCallback
def http_DELETE(self, request):
return super(CalDAVResource, self).http_DELETE(request)
+
@updateCacheTokenOnCallback
def http_ACL(self, request):
return super(CalDAVResource, self).http_ACL(request)
+
##
# WebDAV
##
def liveProperties(self):
baseProperties = (
- element.Owner.qname(), # Private Events needs this but it is also OK to return empty
+ element.Owner.qname(), # Private Events needs this but it is also OK to return empty
)
-
+
if self.isPseudoCalendarCollection():
baseProperties += (
caldavxml.SupportedCalendarComponentSet.qname(),
@@ -407,7 +436,7 @@
if self.isCalendarCollection():
baseProperties += (
element.ResourceID.qname(),
-
+
# These are "live" properties in the sense of WebDAV, however "live" for twext actually means
# ones that are also always present, but the default alarm properties are allowed to be absent
# and are in fact stored in the property store.
@@ -440,13 +469,13 @@
baseProperties += (
caldavxml.ScheduleTag.qname(),
)
-
+
if config.EnableSyncReport and (element.Report(element.SyncCollection(),) in self.supportedReports()):
baseProperties += (element.SyncToken.qname(),)
-
+
if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection()):
baseProperties += (element.AddMember.qname(),)
-
+
if config.Sharing.Enabled:
if config.Sharing.Calendars.Enabled and self.isCalendarCollection():
baseProperties += (
@@ -460,9 +489,10 @@
customxml.Invite.qname(),
customxml.AllowedSharingModes.qname(),
)
-
+
return super(CalDAVResource, self).liveProperties() + baseProperties
+
def isShadowableProperty(self, qname):
"""
Shadowable properties are ones on shared resources where a "default" exists until
@@ -474,6 +504,7 @@
carddavxml.AddressBookDescription.qname(),
)
+
def isGlobalProperty(self, qname):
"""
A global property is one that is the same for all users.
@@ -488,12 +519,13 @@
else:
return False
+
@inlineCallbacks
def hasProperty(self, property, request):
"""
Need to special case schedule-calendar-transp for backwards compatability.
"""
-
+
if type(property) is tuple:
qname = property
else:
@@ -506,7 +538,7 @@
p = self.deadProperties().contains(qname, uid=ownerPrincipal.principalUID())
if p:
returnValue(p)
-
+
elif (not self.isGlobalProperty(qname)):
result = (yield self._hasSharedProperty(qname, request))
returnValue(result)
@@ -514,6 +546,7 @@
res = (yield self._hasGlobalProperty(property, request))
returnValue(res)
+
@inlineCallbacks
def _hasSharedProperty(self, qname, request):
@@ -530,11 +563,12 @@
p = self.deadProperties().contains(qname, uid=ownerPrincipal.principalUID())
returnValue(p)
+
def _hasGlobalProperty(self, property, request):
"""
Need to special case schedule-calendar-transp for backwards compatability.
"""
-
+
if type(property) is tuple:
qname = property
else:
@@ -543,10 +577,11 @@
# Force calendar collections to always appear to have the property
if qname == caldavxml.ScheduleCalendarTransp.qname() and self.isCalendarCollection():
return succeed(True)
-
+
else:
return super(CalDAVResource, self).hasProperty(property, request)
+
@inlineCallbacks
def readProperty(self, property, request):
if type(property) is tuple:
@@ -575,7 +610,6 @@
returnValue(customxml.PubSubXMPPPushKeyProperty())
-
if isvirt:
if self.isShadowableProperty(qname):
ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
@@ -584,7 +618,7 @@
returnValue(p)
except PropertyNotFoundError:
pass
-
+
elif (not self.isGlobalProperty(qname)):
result = (yield self._readSharedProperty(qname, request))
returnValue(result)
@@ -592,6 +626,7 @@
res = (yield self._readGlobalProperty(qname, property, request))
returnValue(res)
+
@inlineCallbacks
def _readSharedProperty(self, qname, request):
@@ -600,6 +635,7 @@
p = self.deadProperties().get(qname, uid=ownerPrincipal.principalUID())
returnValue(p)
+
@inlineCallbacks
def _readGlobalProperty(self, qname, property, request):
@@ -698,7 +734,7 @@
elif qname == customxml.Invite.qname():
if config.Sharing.Enabled and (
- config.Sharing.Calendars.Enabled and self.isCalendarCollection() or
+ config.Sharing.Calendars.Enabled and self.isCalendarCollection() or
config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection()
):
result = (yield self.inviteProperty(request))
@@ -712,7 +748,7 @@
elif qname == customxml.SharedURL.qname():
isvirt = self.isVirtualShare()
-
+
if isvirt:
returnValue(customxml.SharedURL(element.HRef.fromString(self._share.hosturl)))
else:
@@ -721,12 +757,13 @@
result = (yield super(CalDAVResource, self).readProperty(property, request))
returnValue(result)
+
@inlineCallbacks
def writeProperty(self, property, request):
assert isinstance(property, element.WebDAVElement), (
"%r is not a WebDAVElement instance" % (property,)
)
-
+
# Per-user Dav props currently only apply to a sharee's copy of a calendar
isvirt = self.isVirtualShare()
if isvirt and (self.isShadowableProperty(property.qname()) or (not self.isGlobalProperty(property.qname()))):
@@ -734,10 +771,11 @@
ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
p = self.deadProperties().set(property, uid=ownerPrincipal.principalUID())
returnValue(p)
-
+
res = (yield self._writeGlobalProperty(property, request))
returnValue(res)
+
@inlineCallbacks
def _preProcessWriteProperty(self, property, request, isShare=False):
if property.qname() == caldavxml.SupportedCalendarComponentSet.qname():
@@ -782,7 +820,7 @@
responsecode.FORBIDDEN,
"Property %s may only be set on calendar or home collection." % (property,)
))
-
+
if not property.valid():
raise HTTPError(ErrorResponse(
responsecode.CONFLICT,
@@ -799,7 +837,7 @@
# For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
principal = (yield self.resourceOwnerPrincipal(request))
-
+
# Map owner to their inbox
inboxURL = principal.scheduleInboxURL()
if inboxURL:
@@ -807,62 +845,15 @@
myurl = (yield self.canonicalURL(request))
inbox.processFreeBusyCalendar(myurl, property.children[0] == caldavxml.Opaque())
+
@inlineCallbacks
def _writeGlobalProperty(self, property, request):
yield self._preProcessWriteProperty(property, request)
-
- if property.qname() == element.ResourceType.qname():
- if self.isCalendarCollection() or self.isAddressBookCollection():
- sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
- if sawShare:
- if self.isCalendarCollection() and not (config.Sharing.Enabled and config.Sharing.Calendars.Enabled):
- raise HTTPError(StatusResponse(
- responsecode.FORBIDDEN,
- "Cannot create shared calendars on this server.",
- ))
- elif self.isAddressBookCollection() and not (config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled):
- raise HTTPError(StatusResponse(
- responsecode.FORBIDDEN,
- "Cannot create shared address books on this server.",
- ))
-
- # Check if adding or removing share
- shared = (yield self.isShared(request))
- for child in property.children:
- if child.qname() == element.Collection.qname():
- break
- else:
- raise HTTPError(StatusResponse(
- responsecode.FORBIDDEN,
- "Protected property %s may not be set." % (property.sname(),)
- ))
- for child in property.children:
- if self.isCalendarCollection and child.qname() == caldavxml.Calendar.qname() or \
- self.isAddressBookCollection and child.qname() == carddavxml.AddressBook.qname():
- break
- else:
- raise HTTPError(StatusResponse(
- responsecode.FORBIDDEN,
- "Protected property %s may not be set." % (property.sname(),)
- ))
- sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
- if not shared and sawShare:
- # Owner is trying to share a collection
- self.upgradeToShare()
- elif shared and not sawShare:
- # Remove share
- yield self.downgradeFromShare(request)
- returnValue(None)
- else:
- # resourcetype cannot be changed but we will allow it to be set to the same value
- currentType = self.resourceType()
- if currentType == property:
- returnValue(None)
-
result = (yield super(CalDAVResource, self).writeProperty(property, request))
returnValue(result)
+
##
# ACL
##
@@ -874,6 +865,7 @@
"""
return ""
+
def _set_accessMode(self, value):
raise NotImplementedError
@@ -931,9 +923,10 @@
newacls.extend(acls.children)
acls = element.ACL(*newacls)
-
+
returnValue(acls)
+
@inlineCallbacks
def owner(self, request):
"""
@@ -951,6 +944,7 @@
else:
returnValue(None)
+
@inlineCallbacks
def ownerPrincipal(self, request):
"""
@@ -1011,7 +1005,7 @@
def displayName(self):
if self.isAddressBookCollection() and not self.hasDeadProperty((dav_namespace, "displayname")):
return None
-
+
if 'record' in dir(self):
if self.record.fullName:
return self.record.fullName
@@ -1025,15 +1019,18 @@
result = self.name()
return result
+
def name(self):
return None
+
def resourceID(self):
if not self.hasDeadProperty(element.ResourceID.qname()):
uuidval = uuid.uuid4()
self.writeDeadProperty(element.ResourceID(element.HRef.fromString(uuidval.urn)))
return str(self.readDeadProperty(element.ResourceID.qname()).children[0])
+
##
# CalDAV
##
@@ -1044,26 +1041,31 @@
"""
return self.isSpecialCollection(caldavxml.Calendar)
+
def isAddressBookCollection(self):
"""
See L{ICalDAVResource.isAddressBookCollection}.
"""
return self.isSpecialCollection(carddavxml.AddressBook)
+
def isNotificationCollection(self):
"""
See L{ICalDAVResource.isNotificationCollection}.
"""
return self.isSpecialCollection(customxml.Notification)
+
def isDirectoryBackedAddressBookCollection(self): # ATM - temporary fix? (this one worked)
return False
+
def isSpecialCollection(self, collectiontype):
"""
See L{ICalDAVResource.isSpecialCollection}.
"""
- if not self.isCollection(): return False
+ if not self.isCollection():
+ return False
try:
resourcetype = self.resourceType()
@@ -1074,18 +1076,22 @@
return False
return bool(resourcetype.childrenOfType(collectiontype))
+
def isPseudoCalendarCollection(self):
"""
See L{ICalDAVResource.isPseudoCalendarCollection}.
"""
return self.isCalendarCollection()
+
def findCalendarCollections(self, depth, request, callback, privileges=None):
return self.findSpecialCollections(caldavxml.Calendar, depth, request, callback, privileges)
+
def findAddressBookCollections(self, depth, request, callback, privileges=None):
return self.findSpecialCollections(carddavxml.AddressBook, depth, request, callback, privileges)
+
@inlineCallbacks
def findSpecialCollectionsFaster(self, type, depth, request, callback, privileges=None):
assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
@@ -1103,13 +1109,13 @@
continue
if child.isSpecialCollection(type):
callback(child, childpath)
-
+
# No more regular collections. If we leave this in then dropbox is scanned at depth:infinity
# and that is very painful as it requires scanning all calendar resources too. Eventually we need
# to fix drop box and probably re-enable this for the generic case.
# elif child.isCollection():
# if depth == "infinity":
- # yield child.findSpecialCollectionsFaster(type, depth, request, callback, privileges)
+ # yield child.findSpecialCollectionsFaster(type, depth, request, callback, privileges)
findSpecialCollections = findSpecialCollectionsFaster
@@ -1121,7 +1127,7 @@
@param request:
@type request:
"""
-
+
# For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
@@ -1129,12 +1135,13 @@
inbox = (yield request.locateResource(inboxURL))
inbox.processFreeBusyCalendar(request.path, False)
+
@inlineCallbacks
def movedCalendar(self, request, defaultCalendarType, destination, destination_uri):
"""
Calendar has been moved. Need to do some extra clean-up.
"""
-
+
# For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
@@ -1144,26 +1151,28 @@
inbox = (yield request.locateResource(inboxURL))
inbox.processFreeBusyCalendar(request.path, False)
inbox.processFreeBusyCalendar(destination_uri, destination.isCalendarOpaque())
-
+
# Adjust the default calendar setting if necessary
if defaultCalendarType is not None:
- yield inbox.writeProperty(defaultCalendarType(element.HRef(destination_path)), request)
+ yield inbox.writeProperty(defaultCalendarType(element.HRef(destination_path)), request)
+
def isCalendarOpaque(self):
-
+
assert self.isCalendarCollection()
-
+
if self.hasDeadProperty((caldav_namespace, "schedule-calendar-transp")):
property = self.readDeadProperty((caldav_namespace, "schedule-calendar-transp"))
return property.children[0] == caldavxml.Opaque()
else:
return False
+
@inlineCallbacks
def isDefaultCalendar(self, request):
-
+
assert self.isCalendarCollection()
-
+
# Not allowed to delete the default calendar
principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
@@ -1174,6 +1183,7 @@
returnValue(None)
+
@inlineCallbacks
def iCalendarForUser(self, request):
@@ -1207,6 +1217,7 @@
return principal
return None
+
def principalForUID(self, principalUID):
for principalCollection in self.principalCollections():
principal = principalCollection.principalForUID(principalUID)
@@ -1220,19 +1231,20 @@
"""
AddressBook has been moved. Need to do some extra clean-up.
"""
-
+
# Adjust the default addressbook setting if necessary
if defaultAddressBook:
principal = (yield self.resourceOwnerPrincipal(request))
home = (yield principal.addressBookHome(request))
(_ignore_scheme, _ignore_host, destination_path, _ignore_query, _ignore_fragment) = urlsplit(normalizeURL(destination_uri))
- yield home.writeProperty(carddavxml.DefaultAddressBookURL(element.HRef(destination_path)), request)
+ yield home.writeProperty(carddavxml.DefaultAddressBookURL(element.HRef(destination_path)), request)
+
@inlineCallbacks
def isDefaultAddressBook(self, request):
-
+
assert self.isAddressBookCollection()
-
+
# Not allowed to delete the default address book
principal = (yield self.resourceOwnerPrincipal(request))
home = (yield principal.addressBookHome(request))
@@ -1244,6 +1256,7 @@
returnValue(False)
+
@inlineCallbacks
def vCard(self):
"""
@@ -1289,6 +1302,7 @@
result.append(element.Report(element.SyncCollection(),))
return result
+
def writeNewACEs(self, newaces):
"""
Write a new ACL to the resource's property store. We override this for calendar collections
@@ -1316,6 +1330,7 @@
# Do inherited with possibly modified set of aces
super(CalDAVResource, self).writeNewACEs(edited_aces)
+
##
# Utilities
##
@@ -1328,15 +1343,16 @@
"""
return request.locateResource(parentForURL(uri))
+
@inlineCallbacks
def canonicalURL(self, request):
-
+
if not hasattr(self, "_canonical_url"):
-
+
myurl = request.urlForResource(self)
_ignore_scheme, _ignore_host, path, _ignore_query, _ignore_fragment = urlsplit(normalizeURL(myurl))
lastpath = path.split("/")[-1]
-
+
parent = (yield request.locateResource(parentForURL(myurl)))
if parent and isinstance(parent, CalDAVResource):
canonical_parent = (yield parent.canonicalURL(request))
@@ -1346,6 +1362,7 @@
returnValue(self._canonical_url)
+
##
# Quota
##
@@ -1355,18 +1372,20 @@
Quota root only ever set on calendar homes.
"""
return False
-
+
+
def quotaRoot(self, request):
"""
Quota root only ever set on calendar homes.
"""
- return None
+ return None
+
@inlineCallbacks
def quotaRootResource(self, request):
"""
Return the quota root for this resource.
-
+
@return: L{DAVResource} or C{None}
"""
@@ -1390,9 +1409,9 @@
returnValue(result)
+
# Collection sync stuff
-
@inlineCallbacks
def whatchanged(self, client_token, depth):
current_token = (yield self.getSyncToken())
@@ -1405,7 +1424,7 @@
raise ValueError
caluuid, revision = client_token[6:].split("_", 1)
revision = int(revision)
-
+
# Check client token validity
if caluuid != current_uuid:
raise ValueError
@@ -1431,25 +1450,29 @@
returnValue((changed, removed, notallowed, current_token))
+
def _indexWhatChanged(self, revision, depth):
# Now handled directly by newstore
raise NotImplementedError
+
@inlineCallbacks
def getSyncToken(self):
"""
Return current sync-token value.
"""
-
+
internal_token = (yield self.getInternalSyncToken())
returnValue("data:,%s" % (internal_token,))
+
def getInternalSyncToken(self):
"""
Return current internal sync-token value.
"""
raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "Property not supported"))
+
#
# Stuff from CalDAVFile
#
@@ -1460,18 +1483,18 @@
We override the base class to handle the special implicit scheduling weak ETag behavior
for compatibility with old clients using If-Match.
"""
-
+
if config.Scheduling.CalDAV.ScheduleTagCompatibility:
-
+
if self.exists() and hasattr(self, "scheduleEtags"):
etags = self.scheduleEtags
if len(etags) > 1:
# This is almost verbatim from twext.web2.static.checkPreconditions
if request.method not in ("GET", "HEAD"):
-
+
# Always test against the current etag first just in case schedule-etags is out of sync
etag = (yield self.etag())
- etags = (etag, ) + tuple([http_headers.ETag(etag) for etag in etags])
+ etags = (etag,) + tuple([http_headers.ETag(etag) for etag in etags])
# Loop over each tag and succeed if any one matches, else re-raise last exception
exists = self.exists()
@@ -1481,9 +1504,9 @@
try:
http.checkPreconditions(
request,
- entityExists = exists,
- etag = etag,
- lastModified = last_modified,
+ entityExists=exists,
+ etag=etag,
+ lastModified=last_modified,
)
except HTTPError, e:
last_exception = e
@@ -1492,7 +1515,7 @@
else:
if last_exception:
raise last_exception
-
+
# Check per-method preconditions
method = getattr(self, "preconditions_" + request.method, None)
if method:
@@ -1503,6 +1526,7 @@
result = (yield super(CalDAVResource, self).checkPreconditions(request))
returnValue(result)
+
@inlineCallbacks
def createCalendar(self, request):
"""
@@ -1551,7 +1575,7 @@
customxml.MaxCollections(),
"Too many calendar collections",
))
-
+
returnValue((yield self.createCalendarCollection()))
@@ -1569,9 +1593,7 @@
"""
Only implemented by calendar collections; see storebridge.
"""
-
-
@inlineCallbacks
def iCalendarFiltered(self, isowner, accessUID=None):
@@ -1646,9 +1668,10 @@
customxml.MaxCollections(),
"Too many address book collections",
))
-
+
returnValue((yield self.createAddressBookCollection()))
+
def createAddressBookCollection(self):
"""
Internal API for creating an addressbook collection.
@@ -1658,9 +1681,10 @@
"""
return fail(NotImplementedError())
+
@inlineCallbacks
def vCardRolledup(self, request):
- # TODO: just catenate all the vCards together
+ # TODO: just catenate all the vCards together
yield fail(HTTPError((ErrorResponse(responsecode.BAD_REQUEST))))
@@ -1697,6 +1721,7 @@
return super(CalDAVResource, self).supportedPrivileges(request)
+
##
# Quota
##
@@ -1743,6 +1768,7 @@
# return succeed(self.fp.getsize())
return succeed(0)
+
##
# Utilities
##
@@ -1757,7 +1783,8 @@
@return: True if the supplied URI is a child resource
False if not
"""
- if uri is None: return False
+ if uri is None:
+ return False
#
# Parse the URI
@@ -1776,6 +1803,7 @@
return path.startswith(parent) and (len(path) > len(parent)) and (not immediateChild or (path.find("/", len(parent)) == -1))
+
@inlineCallbacks
def _checkParents(self, request, test):
"""
@@ -1790,13 +1818,16 @@
while True:
parent_uri = parentForURL(parent_uri)
- if not parent_uri: break
+ if not parent_uri:
+ break
parent = yield request.locateResource(parent_uri)
if test(parent):
returnValue(parent)
+
+
class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
"""
CalDAV principal collection.
@@ -1806,18 +1837,23 @@
def isCollection(self):
return True
+
def isCalendarCollection(self):
return False
+
def isAddressBookCollection(self):
return False
+
def isDirectoryBackedAddressBookCollection(self):
return False
+
def principalForCalendarUserAddress(self, address):
return None
+
def supportedReports(self):
"""
Principal collections are the only resources supporting the
@@ -1827,6 +1863,7 @@
result.append(element.Report(element.PrincipalSearchPropertySet(),))
return result
+
def principalSearchPropertySet(self):
return element.PrincipalSearchPropertySet(
element.PrincipalSearchProperty(
@@ -1835,7 +1872,7 @@
),
element.Description(
element.PCDATAElement("Display Name"),
- **{"xml:lang":"en"}
+ **{"xml:lang": "en"}
),
),
element.PrincipalSearchProperty(
@@ -1844,11 +1881,13 @@
),
element.Description(
element.PCDATAElement("Calendar User Addresses"),
- **{"xml:lang":"en"}
+ **{"xml:lang": "en"}
),
),
)
+
+
class CalendarPrincipalResource (CalDAVComplianceMixIn, DAVResourceWithChildrenMixin, DAVPrincipalResource):
"""
CalDAV principal resource.
@@ -1858,22 +1897,22 @@
implements(ICalendarPrincipalResource)
def liveProperties(self):
-
+
baseProperties = ()
-
+
if self.calendarsEnabled():
baseProperties += (
- (caldav_namespace, "calendar-home-set" ),
+ (caldav_namespace, "calendar-home-set"),
(caldav_namespace, "calendar-user-address-set"),
- (caldav_namespace, "schedule-inbox-URL" ),
- (caldav_namespace, "schedule-outbox-URL" ),
- (caldav_namespace, "calendar-user-type" ),
- (calendarserver_namespace, "calendar-proxy-read-for" ),
- (calendarserver_namespace, "calendar-proxy-write-for" ),
- (calendarserver_namespace, "auto-schedule" ),
- (calendarserver_namespace, "auto-schedule-mode" ),
+ (caldav_namespace, "schedule-inbox-URL"),
+ (caldav_namespace, "schedule-outbox-URL"),
+ (caldav_namespace, "calendar-user-type"),
+ (calendarserver_namespace, "calendar-proxy-read-for"),
+ (calendarserver_namespace, "calendar-proxy-write-for"),
+ (calendarserver_namespace, "auto-schedule"),
+ (calendarserver_namespace, "auto-schedule-mode"),
)
-
+
if self.addressBooksEnabled():
baseProperties += (carddavxml.AddressBookHomeSet.qname(),)
if self.directoryAddressBookEnabled():
@@ -1887,18 +1926,23 @@
return super(CalendarPrincipalResource, self).liveProperties() + baseProperties
+
def isCollection(self):
return True
+
def calendarsEnabled(self):
return config.EnableCalDAV
+
def addressBooksEnabled(self):
return config.EnableCardDAV
+
def directoryAddressBookEnabled(self):
return config.DirectoryAddressBook.Enabled and config.EnableSearchAddressBook
+
@inlineCallbacks
def readProperty(self, property, request):
if type(property) is tuple:
@@ -1984,6 +2028,7 @@
result = (yield super(CalendarPrincipalResource, self).readProperty(property, request))
returnValue(result)
+
def calendarFreeBusyURIs(self, request):
def gotInbox(inbox):
if inbox is None:
@@ -2008,12 +2053,14 @@
d.addCallback(gotInbox)
return d
+
def scheduleInbox(self, request):
"""
@return: the deferred schedule inbox for this principal.
"""
return request.locateResource(self.scheduleInboxURL())
+
##
# Quota
##
@@ -2023,30 +2070,33 @@
Quota root only ever set on calendar homes.
"""
return False
-
+
+
def quotaRoot(self, request):
"""
Quota root only ever set on calendar homes.
"""
return None
+
+
class DefaultAlarmPropertyMixin(object):
"""
A mixin for use with calendar home and calendars to allow direct access to
the default alarm properties in a more useful way that using readProperty.
In particular it will handle inheritance of the property from the home if a
- calendar does not explicitly have the property.
+ calendar does not explicitly have the property.
"""
-
+
def getDefaultAlarm(self, vevent, timed):
-
+
if vevent:
propname = caldavxml.DefaultAlarmVEventDateTime if timed else caldavxml.DefaultAlarmVEventDate
else:
propname = caldavxml.DefaultAlarmVToDoDateTime if timed else caldavxml.DefaultAlarmVToDoDate
-
+
if self.isCalendarCollection():
-
+
# Get from calendar or inherit from home
try:
prop = self.deadProperties().get(propname.qname())
@@ -2063,6 +2113,8 @@
return str(prop) if prop is not None else None
+
+
class CommonHomeResource(PropfindCacheMixin, SharedHomeMixin, CalDAVResource):
"""
Logic common to Calendar and Addressbook home resources.
@@ -2114,21 +2166,24 @@
def _setupProvisions(self):
pass
+
def postCreateHome(self):
pass
+
def liveProperties(self):
props = super(CommonHomeResource, self).liveProperties() + (
(customxml.calendarserver_namespace, "push-transports"),
(customxml.calendarserver_namespace, "pushkey"),
)
-
+
if config.MaxCollectionsPerHome:
props += (customxml.MaxCollections.qname(),)
return props
+
def sharesDB(self):
"""
Retrieve the new-style shares DB wrapper.
@@ -2141,13 +2196,16 @@
def url(self):
return joinURL(self.parent.url(), self.name, "/")
+
def canonicalURL(self, request):
return succeed(self.url())
+
def exists(self):
# FIXME: tests
return True
+
def isCollection(self):
return True
@@ -2180,9 +2238,10 @@
def currentQuotaUse(self, request):
"""
Get the quota use value
- """
+ """
return maybeDeferred(self._newStoreHome.quotaUsedBytes)
+
def supportedReports(self):
result = super(CommonHomeResource, self).supportedReports()
if config.EnableSyncReport and config.EnableSyncReportHome:
@@ -2190,6 +2249,7 @@
result.append(element.Report(element.SyncCollection(),))
return result
+
def _mergeSyncTokens(self, hometoken, notificationtoken):
"""
Merge two sync tokens, choosing the higher revision number of the two,
@@ -2201,6 +2261,7 @@
hometoken = "%s_%s" % (homekey, notrev,)
return hometoken
+
def canShare(self):
raise NotImplementedError
@@ -2213,16 +2274,17 @@
"""
Override to pre-load children in certain collection types for better performance.
"""
-
+
if depth == "1":
yield self._newStoreHome.loadChildren()
-
+
result = (yield super(CommonHomeResource, self).findChildrenFaster(
depth, request, okcallback, badcallback, missingcallback, names, privileges, inherited_aces
))
-
+
returnValue(result)
-
+
+
@inlineCallbacks
def makeChild(self, name):
# Try built-in children first
@@ -2264,7 +2326,7 @@
notifications,
self,
self._newStoreHome,
- principalCollections = self.principalCollections(),
+ principalCollections=self.principalCollections(),
)
self.propagateTransaction(similar)
returnValue(similar)
@@ -2411,6 +2473,7 @@
returnValue((yield super(CommonHomeResource, self).readProperty(property, request)))
+
##
# ACL
##
@@ -2418,12 +2481,15 @@
def owner(self, request):
return succeed(element.HRef(self.principalForRecord().principalURL()))
+
def ownerPrincipal(self, request):
return succeed(self.principalForRecord())
+
def resourceOwnerPrincipal(self, request):
return succeed(self.principalForRecord())
+
def defaultAccessControlList(self):
myPrincipal = self.principalForRecord()
@@ -2451,19 +2517,23 @@
# Give all access to config.AdminPrincipals
aces += config.AdminACEs
-
+
return element.ACL(*aces)
+
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
# Permissions here are fixed, and are not subject to inheritance rules, etc.
return succeed(self.defaultAccessControlList())
+
def principalCollections(self):
return self.parent.principalCollections()
+
def principalForRecord(self):
raise NotImplementedError("Subclass must implement principalForRecord()")
+
@inlineCallbacks
def etag(self):
"""
@@ -2478,15 +2548,19 @@
else:
returnValue(None)
+
def lastModified(self):
return self._newStoreHome.modified() if self._newStoreHome else None
+
def creationDate(self):
return self._newStoreHome.created() if self._newStoreHome else None
+
def notifierID(self, label="default"):
self._newStoreHome.notifierID(label)
+
def notifyChanged(self):
return self._newStoreHome.notifyChanged()
@@ -2496,6 +2570,7 @@
http_MOVE = None
+
class CalendarHomeResource(DefaultAlarmPropertyMixin, CommonHomeResource):
"""
Calendar home collection classmethod.
@@ -2514,11 +2589,11 @@
def liveProperties(self):
-
+
existing = super(CalendarHomeResource, self).liveProperties()
existing += (
caldavxml.SupportedCalendarComponentSets.qname(),
-
+
# These are "live" properties in the sense of WebDAV, however "live" for twext actually means
# ones that are also always present, but the default alarm properties are allowed to be absent
# and are in fact stored in the property store.
@@ -2535,6 +2610,7 @@
)
return existing
+
@inlineCallbacks
def readProperty(self, property, request):
if type(property) is tuple:
@@ -2559,10 +2635,11 @@
else:
prop = caldavxml.SupportedCalendarComponentSets()
returnValue(prop)
-
+
result = (yield super(CalendarHomeResource, self).readProperty(property, request))
returnValue(result)
+
def _setupProvisions(self):
# Cache children which must be of a specific type
@@ -2614,19 +2691,21 @@
def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, type):
"""
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)
+
def getCalendarResourcesForUID(self, uid, allow_shared=False):
"""
Return all child object resources with the specified UID.
-
+
Pass through direct to store.
"""
return self._newStoreHome.getCalendarResourcesForUID(uid, allow_shared)
+
def defaultAccessControlList(self):
myPrincipal = self.principalForRecord()
@@ -2660,7 +2739,7 @@
# Give all access to config.AdminPrincipals
aces += config.AdminACEs
-
+
if config.EnableProxyPrincipals:
# Server may be read only
if config.EnableReadOnlyServer:
@@ -2745,6 +2824,7 @@
returnValue((changed, deleted, notallowed))
+
class AddressBookHomeResource (CommonHomeResource):
"""
Address book home collection resource.
@@ -2763,11 +2843,12 @@
def liveProperties(self):
-
+
return super(AddressBookHomeResource, self).liveProperties() + (
carddavxml.DefaultAddressBookURL.qname(),
)
+
@inlineCallbacks
def readProperty(self, property, request):
if type(property) is tuple:
@@ -2785,15 +2866,16 @@
defaultAddressBook = str(defaultAddressBookProperty.children[0])
adbk = (yield request.locateResource(str(defaultAddressBook)))
if adbk is not None and isAddressBookCollectionResource(adbk) and adbk.exists() and not adbk.isVirtualShare():
- returnValue(defaultAddressBookProperty)
-
+ returnValue(defaultAddressBookProperty)
+
# Default is not valid - we have to try to pick one
defaultAddressBookProperty = (yield self.pickNewDefaultAddressBook(request))
returnValue(defaultAddressBookProperty)
-
+
result = (yield super(AddressBookHomeResource, self).readProperty(property, request))
returnValue(result)
+
@inlineCallbacks
def writeProperty(self, property, request):
assert isinstance(property, element.WebDAVElement)
@@ -2822,6 +2904,7 @@
yield super(AddressBookHomeResource, self).writeProperty(property, request)
+
def _setupProvisions(self):
# Cache children which must be of a specific type
@@ -2832,12 +2915,15 @@
if config.GlobalAddressBook.Enabled:
self._provisionedLinks[config.GlobalAddressBook.Name] = "/addressbooks/public/global/addressbook/"
+
def makeNewStore(self):
return self._associatedTransaction.addressbookHomeWithUID(self.name, create=True), False # Don't care about created
+
def canShare(self):
return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
+
@inlineCallbacks
def makeRegularChild(self, name):
@@ -2893,6 +2979,7 @@
element.HRef(defaultAddressBookURL))
)
+
@inlineCallbacks
def getInternalSyncToken(self):
# The newstore implementation supports this directly
@@ -2900,7 +2987,7 @@
if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
notifcationtoken = yield (yield self.getChild("notification")).getInternalSyncToken()
-
+
# Merge tokens
adbkkey, adbkrev = adbktoken.split("_", 1)
notrev = notifcationtoken.split("_", 1)[1]
@@ -2934,6 +3021,7 @@
returnValue((changed, deleted, notallowed))
+
class GlobalAddressBookResource (ReadOnlyResourceMixIn, CalDAVResource):
"""
Global address book. All we care about is making sure permissions are setup.
@@ -2942,6 +3030,7 @@
def resourceType(self):
return element.ResourceType.sharedaddressbook #@UndefinedVariable
+
def defaultAccessControlList(self):
aces = (
@@ -2956,7 +3045,7 @@
TwistedACLInheritable(),
),
)
-
+
if config.GlobalAddressBook.EnableAnonymousReadAccess:
aces += (
element.ACE(
@@ -2970,11 +3059,13 @@
)
return element.ACL(*aces)
+
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
# Permissions here are fixed, and are not subject to inheritance rules, etc.
return succeed(self.defaultAccessControlList())
+
class AuthenticationWrapper(SuperAuthenticationWrapper):
""" AuthenticationWrapper implementation which allows overriding
@@ -2992,6 +3083,7 @@
self.overrides[path] = dict([(factory.scheme, factory)
for factory in factories])
+
def hook(self, req):
""" Uses the default credentialFactories unless the request is for
one of the overridden paths """
@@ -3003,11 +3095,11 @@
req.credentialFactories = factories
+
##
# Utilities
##
-
def isCalendarCollectionResource(resource):
try:
resource = ICalDAVResource(resource)
@@ -3017,6 +3109,7 @@
return resource.isCalendarCollection()
+
def isPseudoCalendarCollectionResource(resource):
try:
resource = ICalDAVResource(resource)
@@ -3026,6 +3119,7 @@
return resource.isPseudoCalendarCollection()
+
def isAddressBookCollectionResource(resource):
try:
resource = ICalDAVResource(resource)
@@ -3033,4 +3127,3 @@
return False
else:
return resource.isAddressBookCollection()
-
Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py 2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2012-09-19 14:59:42 UTC (rev 9821)
@@ -461,7 +461,9 @@
record.state = "INVALID"
yield self.invitesDB().addOrUpdateRecord(record)
+ returnValue(len(records))
+
def inviteUserToShare(self, userid, cn, ace, summary, request):
""" Send out in invite first, and then add this user to the share list
@param userid:
@@ -715,6 +717,7 @@
returnValue(result)
+ @inlineCallbacks
def _handleInvite(self, request, invitedoc):
def _handleInviteSet(inviteset):
userid = None
@@ -772,85 +775,84 @@
access = set(access)
return (userid, access)
- def _autoShare(isShared, request):
- if not isShared:
- self.upgradeToShare()
+ setDict, removeDict, updateinviteDict = {}, {}, {}
+ okusers = set()
+ badusers = set()
+ for item in invitedoc.children:
+ if isinstance(item, customxml.InviteSet):
+ userid, cn, access, summary = _handleInviteSet(item)
+ setDict[userid] = (cn, access, summary)
- @inlineCallbacks
- def _processInviteDoc(_, request):
- setDict, removeDict, updateinviteDict = {}, {}, {}
+ # Validate each userid on add only
+ uid = (yield self.validUserIDForShare(userid, request))
+ (okusers if uid is not None else badusers).add(userid)
+ elif isinstance(item, customxml.InviteRemove):
+ userid, access = _handleInviteRemove(item)
+ removeDict[userid] = access
+
+ # Treat removed userids as valid as we will fail invalid ones silently
+ okusers.add(userid)
+
+ # Only make changes if all OK
+ if len(badusers) == 0:
okusers = set()
badusers = set()
- for item in invitedoc.children:
- if isinstance(item, customxml.InviteSet):
- userid, cn, access, summary = _handleInviteSet(item)
- setDict[userid] = (cn, access, summary)
+ # Special case removing and adding the same user and treat that as an add
+ sameUseridInRemoveAndSet = [u for u in removeDict.keys() if u in setDict]
+ for u in sameUseridInRemoveAndSet:
+ removeACL = removeDict[u]
+ cn, newACL, summary = setDict[u]
+ updateinviteDict[u] = (cn, removeACL, newACL, summary)
+ del removeDict[u]
+ del setDict[u]
+ for userid, access in removeDict.iteritems():
+ result = (yield self.uninviteUserToShare(userid, access, request))
+ # If result is False that means the user being removed was not
+ # actually invited, but let's not return an error in this case.
+ okusers.add(userid)
+ for userid, (cn, access, summary) in setDict.iteritems():
+ result = (yield self.inviteUserToShare(userid, cn, access, summary, request))
+ (okusers if result else badusers).add(userid)
+ for userid, (cn, removeACL, newACL, summary) in updateinviteDict.iteritems():
+ result = (yield self.inviteUserUpdateToShare(userid, cn, removeACL, newACL, summary, request))
+ (okusers if result else badusers).add(userid)
- # Validate each userid on add only
- uid = (yield self.validUserIDForShare(userid, request))
- (okusers if uid is not None else badusers).add(userid)
- elif isinstance(item, customxml.InviteRemove):
- userid, access = _handleInviteRemove(item)
- removeDict[userid] = access
+ # In this case bad items do not prevent ok items from being processed
+ ok_code = responsecode.OK
+ else:
+ # In this case a bad item causes all ok items not to be processed so failed dependency is returned
+ ok_code = responsecode.FAILED_DEPENDENCY
- # Treat removed userids as valid as we will fail invalid ones silently
- okusers.add(userid)
+ # Do a final validation of the entire set of invites
+ numRecords = (yield self.validateInvites(request))
- # Only make changes if all OK
- if len(badusers) == 0:
- okusers = set()
- badusers = set()
- # Special case removing and adding the same user and treat that as an add
- sameUseridInRemoveAndSet = [u for u in removeDict.keys() if u in setDict]
- for u in sameUseridInRemoveAndSet:
- removeACL = removeDict[u]
- cn, newACL, summary = setDict[u]
- updateinviteDict[u] = (cn, removeACL, newACL, summary)
- del removeDict[u]
- del setDict[u]
- for userid, access in removeDict.iteritems():
- result = (yield self.uninviteUserToShare(userid, access, request))
- # If result is False that means the user being removed was not
- # actually invited, but let's not return an error in this case.
- okusers.add(userid)
- for userid, (cn, access, summary) in setDict.iteritems():
- result = (yield self.inviteUserToShare(userid, cn, access, summary, request))
- (okusers if result else badusers).add(userid)
- for userid, (cn, removeACL, newACL, summary) in updateinviteDict.iteritems():
- result = (yield self.inviteUserUpdateToShare(userid, cn, removeACL, newACL, summary, request))
- (okusers if result else badusers).add(userid)
+ # Set the sharing state on the collection
+ shared = (yield self.isShared(request))
+ if shared and numRecords == 0:
+ yield self.downgradeFromShare(request)
+ elif not shared and numRecords != 0:
+ self.upgradeToShare()
- # In this case bad items do not prevent ok items from being processed
- ok_code = responsecode.OK
- else:
- # In this case a bad item causes all ok items not to be processed so failed dependency is returned
- ok_code = responsecode.FAILED_DEPENDENCY
+ # Create the multistatus response - only needed if some are bad
+ if badusers:
+ xml_responses = []
+ xml_responses.extend([
+ element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(ok_code))
+ for userid in sorted(okusers)
+ ])
+ xml_responses.extend([
+ element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(responsecode.FORBIDDEN))
+ for userid in sorted(badusers)
+ ])
- # Do a final validation of the entire set of invites
- yield self.validateInvites(request)
+ #
+ # Return response
+ #
+ returnValue(MultiStatusResponse(xml_responses))
+ else:
+ returnValue(responsecode.OK)
- # Create the multistatus response - only needed if some are bad
- if badusers:
- xml_responses = []
- xml_responses.extend([
- element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(ok_code))
- for userid in sorted(okusers)
- ])
- xml_responses.extend([
- element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(responsecode.FORBIDDEN))
- for userid in sorted(badusers)
- ])
- #
- # Return response
- #
- returnValue(MultiStatusResponse(xml_responses))
- else:
- returnValue(responsecode.OK)
-
- return self.isShared(request).addCallback(_autoShare, request).addCallback(_processInviteDoc, request)
-
-
@inlineCallbacks
def _xmlHandleInviteReply(self, request, docroot):
yield self.authorize(request, (element.Read(), element.Write()))
Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py 2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py 2012-09-19 14:59:42 UTC (rev 9821)
@@ -38,9 +38,9 @@
class SharingTests(HomeTestCase):
class FakePrincipal(object):
-
+
class FakeRecord(object):
-
+
def __init__(self, name, cuaddr):
self.fullName = name
self.guid = name
@@ -54,12 +54,13 @@
def calendarHome(self, request):
class FakeHome(object):
- def removeShareByUID(self, request, uid):pass
+ def removeShareByUID(self, request, uid):
+ pass
return FakeHome()
-
+
def principalURL(self):
return self.path
-
+
def displayName(self):
return self.displayname
@@ -121,7 +122,7 @@
@inlineCallbacks
- def _doPOST(self, body, resultcode = responsecode.OK):
+ def _doPOST(self, body, resultcode=responsecode.OK):
request = SimpleRequest(self.site, "POST", "/calendar/")
request.headers.setHeader("content-type", MimeType("text", "xml"))
request.stream = MemoryStream(body)
@@ -130,80 +131,61 @@
self.assertEqual(response.code, resultcode)
returnValue(response)
+
def _clearUIDElementValue(self, xml):
-
+
for user in xml.children:
for element in user.children:
if type(element) == customxml.UID:
element.children[0].data = ""
return xml
+
@inlineCallbacks
- def test_upgradeToShareOnCreate(self):
- request = SimpleRequest(self.site, "MKCOL", "/calendar/")
+ def test_upgradeToShare(self):
rtype = self.resource.resourceType()
self.assertEquals(rtype, regularCalendarType)
- propInvite = (yield self.resource.readProperty(customxml.Invite, request))
- self.assertEquals(propInvite, None)
+ isShared = (yield self.resource.isShared(None))
+ self.assertFalse(isShared)
+ isVShared = self.resource.isVirtualShare()
+ self.assertFalse(isVShared)
self.resource.upgradeToShare()
rtype = self.resource.resourceType()
self.assertEquals(rtype, sharedOwnerType)
- propInvite = (yield self.resource.readProperty(customxml.Invite, request))
- self.assertEquals(propInvite, customxml.Invite())
-
- isShared = (yield self.resource.isShared(request))
+ isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
isVShared = self.resource.isVirtualShare()
self.assertFalse(isVShared)
+
@inlineCallbacks
- def test_upgradeToShareAfterCreate(self):
- request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
+ def test_downgradeFromShare(self):
- rtype = self.resource.resourceType()
- self.assertEquals(rtype, regularCalendarType)
- propInvite = (yield self.resource.readProperty(customxml.Invite, request))
- self.assertEquals(propInvite, None)
-
self.resource.upgradeToShare()
rtype = self.resource.resourceType()
self.assertEquals(rtype, sharedOwnerType)
- propInvite = (yield self.resource.readProperty(customxml.Invite, request))
- self.assertEquals(propInvite, customxml.Invite())
-
- isShared = (yield self.resource.isShared(request))
+ isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
isVShared = self.resource.isVirtualShare()
self.assertFalse(isVShared)
- @inlineCallbacks
- def test_downgradeFromShare(self):
- self.resource.writeDeadProperty(sharedOwnerType)
- self.resource.writeDeadProperty(customxml.Invite())
- rtype = self.resource.resourceType()
- self.assertEquals(rtype, sharedOwnerType)
- propInvite = (yield self.resource.readProperty(customxml.Invite, None))
- self.assertEquals(propInvite, customxml.Invite())
-
yield self.resource.downgradeFromShare(None)
rtype = self.resource.resourceType()
self.assertEquals(rtype, regularCalendarType)
- propInvite = (yield self.resource.readProperty(customxml.Invite, None))
- self.assertEquals(propInvite, None)
-
isShared = (yield self.resource.isShared(None))
self.assertFalse(isShared)
isVShared = self.resource.isVirtualShare()
self.assertFalse(isVShared)
+
@inlineCallbacks
def test_POSTaddInviteeAlreadyShared(self):
-
+
self.resource.upgradeToShare()
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -226,15 +208,16 @@
customxml.InviteStatusNoResponse(),
)
))
-
+
isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
isVShared = self.resource.isVirtualShare()
self.assertFalse(isVShared)
+
@inlineCallbacks
def test_POSTaddInviteeNotAlreadyShared(self):
-
+
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
<CS:set>
@@ -256,17 +239,19 @@
customxml.InviteStatusNoResponse(),
)
))
-
+
isShared = (yield self.resource.isShared(None))
self.assertTrue(isShared)
isVShared = (yield self.resource.isVirtualShare())
self.assertFalse(isVShared)
+
@inlineCallbacks
def test_POSTupdateInvitee(self):
-
- self.resource.upgradeToShare()
+ isShared = (yield self.resource.isShared(None))
+ self.assertFalse(isShared)
+
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
<CS:set>
@@ -277,6 +262,9 @@
</CS:share>
""")
+ isShared = (yield self.resource.isShared(None))
+ self.assertTrue(isShared)
+
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
<CS:set>
@@ -287,6 +275,9 @@
</CS:share>
""")
+ isShared = (yield self.resource.isShared(None))
+ self.assertTrue(isShared)
+
propInvite = (yield self.resource.readProperty(customxml.Invite, None))
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
customxml.InviteUser(
@@ -298,11 +289,13 @@
)
))
+
@inlineCallbacks
def test_POSTremoveInvitee(self):
-
- self.resource.upgradeToShare()
+ isShared = (yield self.resource.isShared(None))
+ self.assertFalse(isShared)
+
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
<CS:set>
@@ -313,6 +306,9 @@
</CS:share>
""")
+ isShared = (yield self.resource.isShared(None))
+ self.assertTrue(isShared)
+
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
<CS:remove>
@@ -321,12 +317,16 @@
</CS:share>
""")
+ isShared = (yield self.resource.isShared(None))
+ self.assertFalse(isShared)
+
propInvite = (yield self.resource.readProperty(customxml.Invite, None))
- self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+ self.assertEquals(propInvite, None)
+
@inlineCallbacks
def test_POSTaddMoreInvitees(self):
-
+
self.resource.upgradeToShare()
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -378,9 +378,10 @@
),
))
+
@inlineCallbacks
def test_POSTaddRemoveInvitees(self):
-
+
self.resource.upgradeToShare()
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -428,6 +429,7 @@
),
))
+
@inlineCallbacks
def test_POSTaddRemoveSameInvitee(self):
@@ -478,6 +480,7 @@
),
))
+
@inlineCallbacks
def test_POSTremoveNonInvitee(self):
"""
@@ -521,7 +524,7 @@
""")
propInvite = (yield self.resource.readProperty(customxml.Invite, None))
- self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+ self.assertEquals(propInvite, None)
@inlineCallbacks
@@ -551,14 +554,12 @@
)
propInvite = (yield self.resource.readProperty(customxml.Invite, None))
- self.assertEquals(
- self._clearUIDElementValue(propInvite), customxml.Invite()
- )
+ self.assertEquals(propInvite, None)
@inlineCallbacks
def test_POSTremoveInvalidInvitee(self):
-
+
self.resource.upgradeToShare()
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -596,7 +597,7 @@
customxml.InviteStatusInvalid(),
)
))
-
+
yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
<CS:remove>
@@ -606,7 +607,7 @@
""")
propInvite = (yield self.resource.readProperty(customxml.Invite, None))
- self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+ self.assertEquals(propInvite, None)
@inlineCallbacks
@@ -617,6 +618,8 @@
access.
"""
+ access = "read"
+
def stubWikiAccessMethod(userID, wikiID):
return access
@@ -664,19 +667,21 @@
def locateChild(self, req, segments):
pass
- def renderHTTP(req):
+
+
+ def renderHTTP(self, req):
pass
+
+
def ownerPrincipal(self, req):
return succeed(StubWikiPrincipal())
-
collection = TestCollection()
collection._share = StubShare()
self.site.resource.putChild("wikifoo", StubWikiResource())
request = SimpleRequest(self.site, "GET", "/wikifoo")
# Simulate the wiki server granting Read access
- access = "read"
acl = (yield collection.shareeAccessControlList(request,
wikiAccessMethod=stubWikiAccessMethod))
self.assertFalse("<write/>" in acl.toxml())
@@ -688,6 +693,7 @@
self.assertTrue("<write/>" in acl.toxml())
+
class DatabaseSharingTests(SharingTests):
@inlineCallbacks
@@ -698,5 +704,3 @@
def createDataStore(self):
return self.calendarStore
-
-
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120919/b0b4f3c8/attachment-0001.html>
More information about the calendarserver-changes
mailing list