/usr/lib/python2.7/dist-packages/txdav/caldav/datastore/scheduling/ischedule/resource.py is in calendarserver 5.2+dfsg-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | ##
# Copyright (c) 2005-2014 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 pycalendar.datetime import PyCalendarDateTime
from pycalendar.timezone import PyCalendarTimezone
from twext.web2 import responsecode
from twext.web2.dav.http import ErrorResponse
from twext.web2.dav.noneprops import NonePropertyStore
from twext.web2.dav.util import allDataFromStream
from twext.web2.http import Response, HTTPError, StatusResponse, XMLResponse
from twext.web2.http_headers import MimeType
from twisted.internet.defer import succeed, returnValue, inlineCallbacks
from twisted.python.failure import Failure
from twistedcaldav import caldavxml
from twistedcaldav.config import config
from twistedcaldav.directory.util import transactionFromRequest
from twistedcaldav.extensions import DAVResource, DAVResourceWithoutChildrenMixin
from twistedcaldav.ical import Component
from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
from twistedcaldav.scheduling_store.caldav.resource import deliverSchedulePrivilegeSet
from txdav.caldav.datastore.scheduling.ischedule.dkim import ISCHEDULE_CAPABILITIES
from txdav.caldav.datastore.scheduling.ischedule.scheduler import IScheduleScheduler
from txdav.caldav.datastore.scheduling.ischedule.xml import ischedule_namespace
from txdav.xml import element as davxml
import txdav.caldav.datastore.scheduling.ischedule.xml as ischedulexml
__all__ = [
"IScheduleInboxResource",
]
class IScheduleInboxResource (ReadOnlyNoCopyResourceMixIn, DAVResourceWithoutChildrenMixin, DAVResource):
"""
iSchedule Inbox resource.
Extends L{DAVResource} to provide iSchedule inbox functionality.
"""
def __init__(self, parent, store, podding=False):
"""
@param parent: the parent resource of this one.
"""
assert parent is not None
DAVResource.__init__(self, principalCollections=parent.principalCollections())
self.parent = parent
self._newStore = store
self._podding = podding
def deadProperties(self):
if not hasattr(self, "_dead_properties"):
self._dead_properties = NonePropertyStore(self)
return self._dead_properties
def etag(self):
return succeed(None)
def checkPreconditions(self, request):
return None
def resourceType(self):
return davxml.ResourceType.ischeduleinbox
def contentType(self):
return MimeType.fromString("text/html; charset=utf-8")
def isCollection(self):
return False
def isCalendarCollection(self):
return False
def isPseudoCalendarCollection(self):
return False
def principalForCalendarUserAddress(self, address):
for principalCollection in self.principalCollections():
principal = principalCollection.principalForCalendarUserAddress(address)
if principal is not None:
return principal
return None
def render(self, request):
output = """<html>
<head>
<title>%(rtype)s Inbox Resource</title>
</head>
<body>
<h1>%(rtype)s Inbox Resource.</h1>
</body
</html>""" % {"rtype" : "Podding" if self._podding else "iSchedule", }
response = Response(200, {}, output)
response.headers.setHeader("content-type", MimeType("text", "html"))
return response
def http_GET(self, request):
"""
The iSchedule GET method.
"""
if not request.args or self._podding:
# Do normal GET behavior
return self.render(request)
action = request.args.get("action", ("",))
if len(action) != 1:
raise HTTPError(StatusResponse(
responsecode.BAD_REQUEST,
"Invalid action parameter",
))
action = action[0]
action = {
"capabilities" : self.doCapabilities,
}.get(action, None)
if action is None:
raise HTTPError(StatusResponse(
responsecode.BAD_REQUEST,
"Unknown action action parameter",
))
return action(request)
def doCapabilities(self, request):
"""
Return a list of all timezones known to the server.
"""
# Determine min/max date-time for iSchedule
now = PyCalendarDateTime.getNowUTC()
minDateTime = PyCalendarDateTime(now.getYear(), 1, 1, 0, 0, 0, PyCalendarTimezone(utc=True))
minDateTime.offsetYear(-1)
maxDateTime = PyCalendarDateTime(now.getYear(), 1, 1, 0, 0, 0, PyCalendarTimezone(utc=True))
maxDateTime.offsetYear(10)
result = ischedulexml.QueryResult(
ischedulexml.Capabilities(
ischedulexml.Version.fromString(config.Scheduling.iSchedule.SerialNumber),
ischedulexml.Versions(
ischedulexml.Version.fromString("1.0"),
),
ischedulexml.SchedulingMessages(
ischedulexml.Component(
ischedulexml.Method(name="REQUEST"),
ischedulexml.Method(name="CANCEL"),
ischedulexml.Method(name="REPLY"),
name="VEVENT"
),
ischedulexml.Component(
ischedulexml.Method(name="REQUEST"),
ischedulexml.Method(name="CANCEL"),
ischedulexml.Method(name="REPLY"),
name="VTODO"
),
ischedulexml.Component(
ischedulexml.Method(name="REQUEST"),
name="VFREEBUSY"
),
),
ischedulexml.CalendarDataTypes(
ischedulexml.CalendarDataType(**{
"content-type": "text/calendar",
"version": "2.0",
}),
),
ischedulexml.Attachments(
ischedulexml.External(),
),
ischedulexml.MaxContentLength.fromString(config.MaxResourceSize),
ischedulexml.MinDateTime.fromString(minDateTime.getText()),
ischedulexml.MaxDateTime.fromString(maxDateTime.getText()),
ischedulexml.MaxInstances.fromString(config.MaxAllowedInstances),
ischedulexml.MaxRecipients.fromString(config.MaxAttendeesPerInstance),
ischedulexml.Administrator.fromString(request.unparseURL(params="", querystring="", fragment="")),
),
)
response = XMLResponse(responsecode.OK, result)
response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber))
return response
@inlineCallbacks
def http_POST(self, request):
"""
The server-to-server POST method.
"""
# Need a transaction to work with
txn = transactionFromRequest(request, self._newStore)
# This is a server-to-server scheduling operation.
scheduler = IScheduleScheduler(txn, None, podding=self._podding)
originator = self.loadOriginatorFromRequestHeaders(request)
recipients = self.loadRecipientsFromRequestHeaders(request)
body = (yield allDataFromStream(request.stream))
# Do the POST processing treating this as a non-local schedule
try:
result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, originator, recipients))
except Exception:
ex = Failure()
yield txn.abort()
ex.raiseException()
else:
yield txn.commit()
response = result.response()
if not self._podding:
response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber))
returnValue(response)
def loadOriginatorFromRequestHeaders(self, request):
# Must have Originator header
originator = request.headers.getRawHeaders("originator")
if originator is None or (len(originator) != 1):
self.log.error("iSchedule POST request must have Originator header")
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(ischedule_namespace, "originator-missing"),
"Missing originator",
))
else:
originator = originator[0]
return originator
def loadRecipientsFromRequestHeaders(self, request):
# Get list of Recipient headers
rawRecipients = request.headers.getRawHeaders("recipient")
if rawRecipients is None or (len(rawRecipients) == 0):
self.log.error("%s request must have at least one Recipient header" % (self.method,))
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(ischedule_namespace, "recipient-missing"),
"No recipients",
))
# Recipient header may be comma separated list
recipients = []
for rawRecipient in rawRecipients:
for r in rawRecipient.split(","):
r = r.strip()
if len(r):
recipients.append(r)
return recipients
@inlineCallbacks
def loadCalendarFromRequest(self, request):
# Must be content-type text/calendar
contentType = request.headers.getHeader("content-type")
if contentType is not None and (contentType.mediaType, contentType.mediaSubtype) != ("text", "calendar"):
self.log.error("MIME type %s not allowed in iSchedule POST request" % (contentType,))
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(ischedule_namespace, "invalid-calendar-data-type"),
"Data is not calendar data",
))
# Parse the calendar object from the HTTP request stream
try:
calendar = (yield Component.fromIStream(request.stream))
except:
# FIXME: Bare except
self.log.error("Error while handling iSchedule POST: %s" % (Failure(),))
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(ischedule_namespace, "invalid-calendar-data"),
description="Can't parse calendar data"
))
returnValue(calendar)
##
# ACL
##
def supportedPrivileges(self, request):
return succeed(deliverSchedulePrivilegeSet)
def defaultAccessControlList(self):
privs = (
davxml.Privilege(davxml.Read()),
davxml.Privilege(caldavxml.ScheduleDeliver()),
)
return davxml.ACL(
# DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
davxml.ACE(
davxml.Principal(davxml.All()),
davxml.Grant(*privs),
davxml.Protected(),
),
)
|