Skip to content
Permalink
Browse files

Add-member support. Required making liveProperties a function.

  • Loading branch information...
cyrusdaboo committed Apr 10, 2010
1 parent 9581d3e commit f28c5d6bfeb65ac4fe7e64d7aed72645556a1acc
@@ -31,6 +31,7 @@
__all__ = [
"CurrentUserPrincipal",
"ErrorDescription",
"AddMember",
"SyncCollection",
"SyncToken",
]
@@ -58,6 +59,17 @@ class ErrorDescription(WebDAVTextElement):
name = "error-description"
protected = True

class AddMember (WebDAVElement):
"""
A property on a collection to allow for "anonymous" creation of resources.
(draft-reschke-webdav-post)
"""
name = "add-member"
hidden = True
protected = True

allowed_children = { (dav_namespace, "href"): (0, 1) }

class SyncCollection (WebDAVElement):
"""
DAV report used to retrieve specific calendar component items via their
@@ -129,30 +129,32 @@ class DAVPropertyMixIn (MetaDataMixin):
# meaningful if you are using ACL semantics (ie. Unix-like) which
# use them. This (generic) class does not.

liveProperties = (
(dav_namespace, "resourcetype" ),
(dav_namespace, "getetag" ),
(dav_namespace, "getcontenttype" ),
(dav_namespace, "getcontentlength" ),
(dav_namespace, "getlastmodified" ),
(dav_namespace, "creationdate" ),
(dav_namespace, "displayname" ),
(dav_namespace, "supportedlock" ),
(dav_namespace, "supported-report-set" ), # RFC 3253, section 3.1.5
#(dav_namespace, "owner" ), # RFC 3744, section 5.1
#(dav_namespace, "group" ), # RFC 3744, section 5.2
(dav_namespace, "supported-privilege-set" ), # RFC 3744, section 5.3
(dav_namespace, "current-user-privilege-set"), # RFC 3744, section 5.4
(dav_namespace, "current-user-principal" ), # draft-sanchez-webdav-current-principal
(dav_namespace, "acl" ), # RFC 3744, section 5.5
(dav_namespace, "acl-restrictions" ), # RFC 3744, section 5.6
(dav_namespace, "inherited-acl-set" ), # RFC 3744, section 5.7
(dav_namespace, "principal-collection-set" ), # RFC 3744, section 5.8
(dav_namespace, "quota-available-bytes" ), # RFC 4331, section 3
(dav_namespace, "quota-used-bytes" ), # RFC 4331, section 4

(twisted_dav_namespace, "resource-class"),
)
def liveProperties(self):

return (
(dav_namespace, "resourcetype" ),
(dav_namespace, "getetag" ),
(dav_namespace, "getcontenttype" ),
(dav_namespace, "getcontentlength" ),
(dav_namespace, "getlastmodified" ),
(dav_namespace, "creationdate" ),
(dav_namespace, "displayname" ),
(dav_namespace, "supportedlock" ),
(dav_namespace, "supported-report-set" ), # RFC 3253, section 3.1.5
#(dav_namespace, "owner" ), # RFC 3744, section 5.1
#(dav_namespace, "group" ), # RFC 3744, section 5.2
(dav_namespace, "supported-privilege-set" ), # RFC 3744, section 5.3
(dav_namespace, "current-user-privilege-set"), # RFC 3744, section 5.4
(dav_namespace, "current-user-principal" ), # draft-sanchez-webdav-current-principal
(dav_namespace, "acl" ), # RFC 3744, section 5.5
(dav_namespace, "acl-restrictions" ), # RFC 3744, section 5.6
(dav_namespace, "inherited-acl-set" ), # RFC 3744, section 5.7
(dav_namespace, "principal-collection-set" ), # RFC 3744, section 5.8
(dav_namespace, "quota-available-bytes" ), # RFC 4331, section 3
(dav_namespace, "quota-used-bytes" ), # RFC 4331, section 4

(twisted_dav_namespace, "resource-class"),
)

def deadProperties(self):
"""
@@ -198,7 +200,7 @@ def hasProperty(self, property, request):
return d

return succeed(
qname in self.liveProperties or
qname in self.liveProperties() or
self.deadProperties().contains(qname)
)

@@ -427,7 +429,7 @@ def defer():
qname = property.qname()
sname = property.sname()

if qname in self.liveProperties:
if qname in self.liveProperties():
raise HTTPError(StatusResponse(
responsecode.FORBIDDEN,
"Live property %s cannot be deleted." % (sname,)
@@ -449,7 +451,7 @@ def listProperties(self, request):
"""
See L{IDAVResource.listProperties}.
"""
qnames = set(self.liveProperties)
qnames = set(self.liveProperties())

# Add dynamic live properties that exist
dynamicLiveProperties = (
@@ -2288,12 +2290,14 @@ class DAVPrincipalResource (DAVResource):
# WebDAV
##

liveProperties = DAVResource.liveProperties + (
(dav_namespace, "alternate-URI-set"),
(dav_namespace, "principal-URL" ),
(dav_namespace, "group-member-set" ),
(dav_namespace, "group-membership" ),
)
def liveProperties(self):

return super(DAVPrincipalResource, self).liveProperties() + (
(dav_namespace, "alternate-URI-set"),
(dav_namespace, "principal-URL" ),
(dav_namespace, "group-member-set" ),
(dav_namespace, "group-membership" ),
)

def davComplianceClasses(self):
return ("1", "access-control",)
@@ -47,7 +47,7 @@ class PROP(twext.web2.dav.test.util.TestCase):
"""

def liveProperties(self):
return [lookupElement(qname)() for qname in self.resource_class.liveProperties if (qname[0] == dav_namespace) and qname not in dynamicLiveProperties]
return [lookupElement(qname)() for qname in self.site.resource.liveProperties() if (qname[0] == dav_namespace) and qname not in dynamicLiveProperties]

def test_PROPFIND_basic(self):
"""
@@ -298,6 +298,8 @@ def url(self):
##
#return joinURL(self.parent.parent.getChild(self.record.recordType).url(), self.record.shortName)

def canonicalURL(self, request):
return succeed(self.url())
##
# DAV
##
@@ -2025,7 +2025,7 @@ def readProperty(self, property, request):

def listProperties(self, request):
#print("VCardResource.listProperties()")
qnames = set(self.liveProperties)
qnames = set(self.liveProperties())

# Add dynamic live properties that exist
dynamicLiveProperties = (
@@ -514,11 +514,13 @@ class DirectoryPrincipalResource (PermissionsMixIn, DAVPrincipalResource, DAVFil
Directory principal resource.
"""

liveProperties = tuple(DAVPrincipalResource.liveProperties) + (
(calendarserver_namespace, "first-name" ),
(calendarserver_namespace, "last-name" ),
(calendarserver_namespace, "email-address-set"),
)
def liveProperties(self):

return super(DirectoryPrincipalResource, self).liveProperties() + (
(calendarserver_namespace, "first-name" ),
(calendarserver_namespace, "last-name" ),
(calendarserver_namespace, "email-address-set"),
)

def __init__(self, parent, record):
"""
@@ -821,15 +823,8 @@ class DirectoryCalendarPrincipalResource (DirectoryPrincipalResource, CalendarPr
Directory calendar principal resource.
"""

@property
def liveProperties(self):
# This needs to be a dynamic property because CalendarPrincipalResource
# liveProperties changes on the fly (drop box enabling)
return (
tuple(DirectoryPrincipalResource.liveProperties) +
tuple(CalendarPrincipalResource.liveProperties)
)

return DirectoryPrincipalResource.liveProperties(self) + CalendarPrincipalResource.liveProperties(self)

@inlineCallbacks
def readProperty(self, property, request):
@@ -453,11 +453,12 @@ class DAVPrincipalResource (DirectoryPrincipalPropertySearchMixIn, SuperDAVPrinc
Extended L{twext.web2.dav.static.DAVFile} implementation.
"""

liveProperties = tuple(SuperDAVPrincipalResource.liveProperties) + (
(calendarserver_namespace, "expanded-group-member-set"),
(calendarserver_namespace, "expanded-group-membership"),
(calendarserver_namespace, "record-type"),
)
def liveProperties(self):
return super(DAVPrincipalResource, self).liveProperties() + (
(calendarserver_namespace, "expanded-group-member-set"),
(calendarserver_namespace, "expanded-group-membership"),
(calendarserver_namespace, "record-type"),
)

http_REPORT = http_REPORT

@@ -27,6 +27,7 @@
"get",
"mkcalendar",
"mkcol",
"post",
"propfind",
"put",
"report",
@@ -0,0 +1,169 @@
##
# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

from hashlib import md5

from twext.web2.dav.http import ErrorResponse
from twext.web2.dav.util import allDataFromStream, joinURL
from twext.web2.filter.location import addLocation
from twext.web2.http import HTTPError, StatusResponse

from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.carddavxml import carddav_namespace
from twistedcaldav.method.put_addressbook_common import StoreAddressObjectResource
from twistedcaldav.method.put_common import StoreCalendarObjectResource

import time

"""
CalDAV POST method.
"""

__all__ = ["http_POST"]

from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.web2 import responsecode

from twistedcaldav.config import config

@inlineCallbacks
def http_POST(self, request):

# POST can support many different APIs

# First look at query params
if request.params:
if request.params == "add-member":
if config.EnableAddMember:
result = (yield POST_handler_add_member(self, request))
returnValue(result)

else:
# Content-type handlers
contentType = request.headers.getHeader("content-type")
if contentType:
if hasattr(self, "POST_handler_content_type"):
result = (yield self.POST_handler_content_type(request, (contentType.mediaType, contentType.mediaSubtype)))
returnValue(result)

returnValue(responsecode.FORBIDDEN)

@inlineCallbacks
def POST_handler_add_member(self, request):

# Handle ;add-member
if self.isCalendarCollection():

parentURL = request.path
parent = self

# Content-type check
content_type = request.headers.getHeader("content-type")
if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
self.log_error("MIME type %s not allowed in calendar collection" % (content_type,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))

# Read the calendar component from the stream
try:
calendardata = (yield allDataFromStream(request.stream))
if not hasattr(request, "extendedLogItems"):
request.extendedLogItems = {}
request.extendedLogItems["cl"] = str(len(calendardata)) if calendardata else "0"

# We must have some data at this point
if calendardata is None:
# Use correct DAV:error response
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description="No calendar data"))

# Create a new name if one was not provided
name = md5(str(calendardata) + str(time.time()) + self.fp.path).hexdigest() + ".ics"

# Get a resource for the new item
newchildURL = joinURL(parentURL, name)
newchild = (yield request.locateResource(newchildURL))

storer = StoreCalendarObjectResource(
request = request,
destination = newchild,
destination_uri = newchildURL,
destinationcal = True,
destinationparent = parent,
calendar = calendardata,
)
result = (yield storer.run())

# May need to add a location header
addLocation(request, request.unparseURL(path=newchildURL, params=""))

returnValue(result)

except ValueError, e:
self.log_error("Error while handling (calendar) POST: %s" % (e,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))

elif self.isAddressBookCollection():

parentURL = request.path
parent = self

# Content-type check
content_type = request.headers.getHeader("content-type")
if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "vcard"):
self.log_error("MIME type %s not allowed in address book collection" % (content_type,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "supported-address-data")))

# Read the calendar component from the stream
try:
vcarddata = (yield allDataFromStream(request.stream))
if not hasattr(request, "extendedLogItems"):
request.extendedLogItems = {}
request.extendedLogItems["cl"] = str(len(vcarddata)) if vcarddata else "0"

# We must have some data at this point
if vcarddata is None:
# Use correct DAV:error response
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "valid-address-data"), description="No address data"))

# Create a new name if one was not provided
name = md5(str(vcarddata) + str(time.time()) + self.fp.path).hexdigest() + ".vcf"

# Get a resource for the new item
newchildURL = joinURL(parentURL, name)
newchild = (yield request.locateResource(newchildURL))

storer = StoreAddressObjectResource(
request = request,
sourceadbk = False,
destination = newchild,
destination_uri = newchildURL,
destinationadbk = True,
destinationparent = parent,
vcard = vcarddata,
)
result = (yield storer.run())

# May need to add a location header
addLocation(request, request.unparseURL(path=newchildURL, params=""))

returnValue(result)

except ValueError, e:
self.log_error("Error while handling (calendar) POST: %s" % (e,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))

# Default behavior
returnValue(responsecode.FORBIDDEN)

0 comments on commit f28c5d6

Please sign in to comment.
You can’t perform that action at this time.