def get(self, qname, uid=None, cache=True): if cache: propertyCache = self.propertyCache() qnameuid = qname + (uid, ) if qnameuid in propertyCache: return propertyCache[qnameuid] else: raise HTTPError( StatusResponse( responsecode.NOT_FOUND, "No such property: %s%s" % (uid if uid else "", encodeXMLName(*qname)))) self.log.debug("Read for %s%s on %s" % (("{%s}:" % (uid, )) if uid else "", qname, self.childPropertyStore.resource.fp.path)) return self.childPropertyStore.get(qname, uid=uid)
def listChildren(self): children = [] if config.EnablePrincipalListings: try: for record in (yield self.directory.recordsWithRecordType( self.recordType)): for shortName in getattr(record, "shortNames", []): children.append(shortName) except AttributeError: log.warn("Cannot list children of record type {rt}", rt=self.recordType.name) returnValue(children) else: # Not a listable collection raise HTTPError(responsecode.FORBIDDEN)
def generateFreeBusyResponse(self, recipient, responses, organizerProp, uid, event_details): # Extract the ATTENDEE property matching current recipient from the calendar data cuas = recipient.record.calendarUserAddresses attendeeProp = self.scheduler.calendar.getAttendeeProperty(cuas) try: fbresult = yield FreebusyQuery( organizer=self.scheduler.organizer, organizerProp=organizerProp, recipient=recipient, attendeeProp=attendeeProp, uid=uid, timerange=self.scheduler.timeRange, excludeUID=self.scheduler.excludeUID, logItems=self.scheduler.logItems, event_details=event_details, ).generateAttendeeFreeBusyResponse() except Exception as e: log.failure( "Could not determine free busy information for recipient {cuaddr}", cuaddr=recipient.cuaddr, level=LogLevel.debug ) log.error( "Could not determine free busy information for recipient {cuaddr}: {ex}", cuaddr=recipient.cuaddr, ex=e ) err = HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions"), "Could not determine free busy information", )) responses.add( recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.NO_AUTHORITY ) returnValue(False) else: responses.add( recipient.cuaddr, responsecode.OK, reqstatus=iTIPRequestStatus.SUCCESS, calendar=fbresult ) returnValue(True)
def xmlRequestHandler(self, request): # Need to read the data and get the root element first xmldata = (yield allDataFromStream(request.stream)) try: doc = element.WebDAVDocument.fromString(xmldata) except ValueError, e: self.log.error("Error parsing doc (%s) Doc:\n %s" % ( str(e), xmldata, )) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request"), "Invalid XML", ))
def generateFreeBusyResponse(self, recipient, responses, organizerProp, organizerPrincipal, uid, event_details): # Extract the ATTENDEE property matching current recipient from the calendar data cuas = recipient.record.calendarUserAddresses attendeeProp = self.scheduler.calendar.getAttendeeProperty(cuas) remote = isinstance(self.scheduler.organizer, RemoteCalendarUser) try: fbresult = (yield self.generateAttendeeFreeBusyResponse( recipient, organizerProp, organizerPrincipal, uid, attendeeProp, remote, event_details, )) except Exception: log.failure( "Could not determine free busy information for recipient {cuaddr}", cuaddr=recipient.cuaddr, level=LogLevel.debug ) log.error( "Could not determine free busy information for recipient {cuaddr}", cuaddr=recipient.cuaddr ) err = HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions"), "Could not determine free busy information", )) responses.add( recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.NO_AUTHORITY ) returnValue(False) else: responses.add( recipient.cuaddr, responsecode.OK, reqstatus=iTIPRequestStatus.SUCCESS, calendar=fbresult ) returnValue(True)
def loadOriginatorFromRequestDetails(self, request): # The originator is the owner of the Outbox. We will have checked prior to this # that the authenticated user has privileges to schedule as the owner. originator = "" originatorPrincipal = (yield self.ownerPrincipal(request)) if originatorPrincipal: # Pick the canonical CUA: originator = originatorPrincipal.canonicalCalendarUserAddress() if not originator: self.log.error("{m} request must have Originator", m=self.method) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "originator-specified"), "Missing originator", )) else: returnValue(originator)
def loadRecipientsFromCalendarData(self, calendar): # Get the ATTENDEEs attendees = list() unique_set = set() for attendee, _ignore in calendar.getAttendeesByInstance(): if attendee not in unique_set: attendees.append(attendee) unique_set.add(attendee) if not attendees: self.log.error("POST request must have at least one ATTENDEE") raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-specified"), "Must have recipients", )) else: return(list(attendees))
def readProperty(self, property, request): if type(property) is tuple: qname = property else: qname = property.qname() namespace, name = qname if namespace == dav_namespace: if name == "resourcetype": rtype = self.resourceType() returnValue(rtype) elif namespace == calendarserver_namespace: if name == "expanded-group-member-set": principals = (yield self.expandedGroupMembers()) returnValue(customxml.ExpandedGroupMemberSet( *[element.HRef(p.principalURL()) for p in principals] )) elif name == "expanded-group-membership": principals = (yield self.expandedGroupMemberships()) returnValue(customxml.ExpandedGroupMembership( *[element.HRef(p.principalURL()) for p in principals] )) elif name == "record-type": if hasattr(self, "record"): returnValue( customxml.RecordType( self.record.service.recordTypeToOldName( self.record.recordType ) ) ) else: raise HTTPError(StatusResponse( responsecode.NOT_FOUND, "Property %s does not exist." % (qname,) )) result = (yield super(DAVPrincipalResource, self).readProperty(property, request)) returnValue(result)
def extractCalendarServerPrincipalSearchData(doc): """ Extract relevant info from a CalendarServerPrincipalSearch document @param doc: CalendarServerPrincipalSearch object to extract info from @return: A tuple containing: the list of tokens the context string the applyTo boolean the clientLimit integer the propElement containing the properties to return """ context = doc.attributes.get("context", None) applyTo = False tokens = [] clientLimit = None for child in doc.children: if child.qname() == (dav_namespace, "prop"): propElement = child elif child.qname() == (dav_namespace, "apply-to-principal-collection-set"): applyTo = True elif child.qname() == (calendarserver_namespace, "search-token"): tokenValue = child.toString().strip() if tokenValue: tokens.append(tokenValue) elif child.qname() == (calendarserver_namespace, "limit"): try: nresults = child.childOfType(customxml.NResults) clientLimit = int(str(nresults)) except ( TypeError, ValueError, ): msg = "Bad XML: unknown value for <limit> element" log.warn(msg) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg)) return tokens, context, applyTo, clientLimit, propElement
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) # Check content first contentType = request.headers.getHeader("content-type") format = self.determineType(contentType) if format is None: msg = "MIME type {} not allowed in iSchedule request".format(contentType,) self.log.error(msg) raise HTTPError(scheduler.errorResponse( responsecode.FORBIDDEN, (ischedule_namespace, "invalid-calendar-data-type"), msg, )) originator = self.loadOriginatorFromRequestHeaders(request) recipients = self.loadRecipientsFromRequestHeaders(request) body = (yield allDataFromStream(request.stream)) calendar = Component.fromString(body, format=format) # Do the POST processing treating this as a non-local schedule try: result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, calendar, originator, recipients)) except Exception: ex = Failure() yield txn.abort() ex.raiseException() else: yield txn.commit() response = result.response(format=format) if not self._podding: response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber)) returnValue(response)
def _handleInviteRemove(inviteremove): userid = None access = [] for item in inviteremove.children: if isinstance(item, element.HRef): userid = str(item) continue if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess): access.append(item) continue if userid is None: raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request"), "Missing href: %s" % (inviteremove,), )) if len(access) == 0: access = None else: access = set(access) return (userid, access)
def list(self, uid=None, filterByUID=True): """ Enumerate the property names stored in extended attributes of the wrapped path. @param uid: The per-user identifier for per user properties. @return: A C{list} of property names as two-tuples of namespace URI and local name. """ prefix = self.deadPropertyXattrPrefix try: attrs = iter(self.attrs) except IOError, e: if e.errno == errno.ENOENT: return [] raise HTTPError( StatusResponse(statusForFailure(Failure()), "Unable to list properties: %s", (self.resource.fp.path, )))
def mkcollection(filepath): """ Perform a X{MKCOL} on the given filepath. @param filepath: the L{FilePath} of the collection resource to create. @raise HTTPError: (containing an appropriate response) if the operation fails. @return: a deferred response with a status code of L{responsecode.CREATED} if the destination already exists, or L{responsecode.NO_CONTENT} if the destination was created by the X{MKCOL} operation. """ try: os.mkdir(filepath.path) # Remove stat info from filepath because we modified it filepath.changed() except: raise HTTPError( statusForFailure( Failure(), "creating directory in MKCOL: %s" % (filepath.path, ))) return succeed(responsecode.CREATED)
def renderHTTP(self, request): """ Do the reverse proxy request and return the response. @param request: the incoming request that needs to be proxied. @type request: L{Request} @return: Deferred L{Response} """ self.log.info("{method} {uri} {proto}", method=request.method, uri=request.uri, proto="HTTP/%s.%s" % request.clientproto) # Check for multi-hop if not self.allowMultiHop: x_server = request.headers.getHeader("x-forwarded-server") if x_server: for item in x_server: if item.lower() == config.ServerHostName.lower(): raise HTTPError( StatusResponse(responsecode.BAD_GATEWAY, "Too many x-forwarded-server hops")) clientPool = getHTTPClientPool(self.poolID) proxyRequest = ClientRequest(request.method, request.uri, request.headers, request.stream) # Need x-forwarded-(for|host|server) headers. First strip any existing ones out, then add ours proxyRequest.headers.removeHeader("x-forwarded-host") proxyRequest.headers.removeHeader("x-forwarded-for") proxyRequest.headers.removeHeader("x-forwarded-server") proxyRequest.headers.addRawHeader("x-forwarded-host", request.host) proxyRequest.headers.addRawHeader("x-forwarded-for", request.remoteAddr.host) proxyRequest.headers.addRawHeader("x-forwarded-server", config.ServerHostName) return clientPool.submitRequest(proxyRequest)
def do(action, property, removing=False): """ Perform action(property, request) while maintaining an undo queue. """ has = waitForDeferred(self.hasProperty(property, request)) yield has has = has.getResult() if has: oldProperty = waitForDeferred( self.readProperty(property, request)) yield oldProperty oldProperty = oldProperty.getResult() def undo(): return self.writeProperty(oldProperty, request) else: def undo(): return self.removeProperty(property, request) try: x = waitForDeferred(action(property, request)) yield x x.getResult() except KeyError, e: # Removing a non-existent property is OK according to WebDAV if removing: responses.add(responsecode.OK, property) yield True return else: # Convert KeyError exception into HTTPError responses.add( Failure(exc_value=HTTPError( StatusResponse(responsecode.FORBIDDEN, str( e)))), property) yield False return
def finalChecks(self): """ Final checks before doing the actual scheduling. """ # With implicit scheduling only certain types of iTIP operations are allowed for POST. if self.doingPOST: # Freebusy requests always processed if self.checkForFreeBusy(): return # COUNTER and DECLINE-COUNTER allowed if self.calendar.propertyValue("METHOD") in ("COUNTER", "DECLINECOUNTER"): return # Anything else is not allowed. However, for compatibility we will optionally # return a success response for all attendees. if config.Scheduling.CalDAV.OldDraftCompatibility: self.fakeTheResult = True else: raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid iTIP message for implicit scheduling"))
def _handleInviteSet(inviteset): userid = None cn = None access = None summary = None for item in inviteset.children: if isinstance(item, element.HRef): userid = str(item) continue if isinstance(item, customxml.CommonName): cn = str(item) continue if isinstance(item, customxml.InviteSummary): summary = str(item) continue if isinstance(item, customxml.ReadAccess) or isinstance( item, customxml.ReadWriteAccess): access = item continue if userid and access and summary: return (userid, cn, access, summary) else: error_text = [] if userid is None: error_text.append("missing href") if access is None: error_text.append("missing access") if summary is None: error_text.append("missing summary") raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request"), "%s: %s" % ( ", ".join(error_text), inviteset, ), ))
def delete(self, qname, uid=None): """ Remove the extended attribute from the wrapped path which stores the property given by C{qname}. @param uid: The per-user identifier for per user properties. @param qname: The property to delete as a two-tuple of namespace URI and local name. """ key = self._encode(qname, uid) try: try: self.attrs.remove(key) except KeyError: pass except IOError, e: if e.errno not in _ATTR_MISSING: raise except: raise HTTPError( StatusResponse(statusForFailure(Failure()), "Unable to delete property: %s", (key, )))
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( "{method} request must have at least one Recipient header", method=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
def readProperty(self, property, request): if type(property) is tuple: qname = property else: qname = property.qname() namespace, name = qname if namespace == calendarserver_namespace: if name == "record-type": if hasattr(self.parent, "record"): returnValue( customxml.RecordType( self._recordTypeFromProxyType().description)) else: raise HTTPError( StatusResponse( responsecode.NOT_FOUND, "Property %s does not exist." % (qname, ))) result = (yield super(CalendarUserProxyPrincipalResource, self).readProperty(property, request)) returnValue(result)
def http_POST(self, request): # POST can only target an existing resource if not self.exists(): log.error("Resource not found: {s!r}", s=self) raise HTTPError(responsecode.NOT_FOUND) # POST can support many different APIs # First look at query params if request.params: if request.params == "add-member": if config.EnableAddMember and hasattr(self, "POST_handler_add_member"): request.submethod = "add-member" result = (yield self.POST_handler_add_member(request)) returnValue(result) # Look for query arguments if request.args: action = request.args.get("action", ("", )) if len(action) == 1: action = action[0] if hasattr(self, "POST_handler_action"): request.submethod = action result = (yield self.POST_handler_action(request, action)) returnValue(result) # 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)
def contains(self, qname, uid=None): """ Determine whether the property given by C{qname} is stored in an extended attribute of the wrapped path. @param qname: The property to look up as a two-tuple of namespace URI and local name. @param uid: The per-user identifier for per user properties. @return: C{True} if the property exists, C{False} otherwise. """ key = self._encode(qname, uid) try: self.attrs.get(key) except KeyError: return False except IOError, e: if e.errno in _ATTR_MISSING or e.errno == errno.ENOENT: return False raise HTTPError( StatusResponse(statusForFailure(Failure()), "Unable to read property: %s" % (key, )))
def http_DELETE(self, request): """ Respond to a DELETE request. (RFC 2518, section 8.6) """ if not self.exists(): log.error("File not found: %s" % (self, )) raise HTTPError(responsecode.NOT_FOUND) depth = request.headers.getHeader("depth", "infinity") # # Check authentication and access controls # parent = waitForDeferred(request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Unbind(), ))) yield x x.getResult() x = waitForDeferred(deleteResource(request, self, request.uri, depth)) yield x yield x.getResult()
def checkSACL(self, request): """ Check SACLs against the current request """ topLevel = request.path.strip("/").split("/")[0] saclServices = self.saclMap.get(topLevel, None) if not saclServices: returnValue(True) try: authnUser, authzUser = yield self.authenticate(request) except Exception: response = (yield UnauthorizedResponse.makeResponse( request.credentialFactories, request.remoteAddr )) raise HTTPError(response) # SACLs are enabled in the plist, but there may not actually # be a SACL group assigned to this service. Let's see if # unauthenticated users are allowed by calling CheckSACL # with an empty string. if authzUser is None: for saclService in saclServices: if checkSACL("", saclService): # No group actually exists for this SACL, so allow # unauthenticated access returnValue(True) # There is a SACL group for at least one of the SACLs, so no # unauthenticated access response = (yield UnauthorizedResponse.makeResponse( request.credentialFactories, request.remoteAddr )) log.info("Unauthenticated user denied by SACLs") raise HTTPError(response) # Cache the authentication details request.authnUser = authnUser request.authzUser = authzUser # Figure out the "username" from the davxml.Principal object username = authzUser.record.shortNames[0] access = False for saclService in saclServices: if checkSACL(username, saclService): # Access is allowed access = True break # Mark SACLs as having been checked so we can avoid doing it # multiple times request.checkedSACL = True if access: returnValue(True) log.warn( "User {user!r} is not enabled with the {sacl!r} SACL(s)", user=username, sacl=saclServices ) raise HTTPError(responsecode.FORBIDDEN)
def locateChild(self, request, segments): for filter in self.contentFilters: request.addResponseFilter(filter[0], atEnd=filter[1]) # Examine cookies for wiki auth token; if there, ask the paired wiki # server for the corresponding record name. If that maps to a # principal, assign that to authnuser. # Also, certain non-browser clients send along the wiki auth token # sometimes, so we now also look for the presence of x-requested-with # header that the webclient sends. However, in the case of a GET on # /webcal that header won't be sent so therefore we allow wiki auth # for any path in the authServiceMap even if that header is missing. allowWikiAuth = False topLevel = request.path.strip("/").split("/")[0] if self.authServiceMap.get(topLevel, False): allowWikiAuth = True if not hasattr(request, "checkedWiki"): # Only do this once per request request.checkedWiki = True wikiConfig = config.Authentication.Wiki cookies = request.headers.getHeader("cookie") requestedWith = request.headers.hasHeader("x-requested-with") if ( wikiConfig["Enabled"] and (requestedWith or allowWikiAuth) and cookies is not None ): for cookie in cookies: if cookie.name == wikiConfig["Cookie"]: token = cookie.value break else: token = None if token is not None and token != "unauthenticated": log.debug( "Wiki sessionID cookie value: {token}", token=token ) try: uid = yield uidForAuthToken(token, wikiConfig["EndpointDescriptor"]) if uid == "unauthenticated": uid = None except WebError as w: uid = None # FORBIDDEN status means it's an unknown token if int(w.status) == responsecode.NOT_FOUND: log.debug( "Unknown wiki token: {token}", token=token ) else: log.error( "Failed to look up wiki token {token}: {msg}", token=token, msg=w.message ) except Exception as e: log.error( "Failed to look up wiki token: {error}", error=e ) uid = None if uid is not None: log.debug( "Wiki lookup returned uid: {uid}", uid=uid ) principal = yield self.principalForUID(request, uid) if principal: log.debug( "Wiki-authenticated principal {uid} " "being assigned to authnUser and authzUser", uid=uid ) request.authzUser = request.authnUser = principal if not hasattr(request, "authzUser") and config.WebCalendarAuthPath: topLevel = request.path.strip("/").split("/")[0] if self.authServiceMap.get(topLevel, False): # We've not been authenticated and the auth service is enabled # for this resource, so redirect. # Use config.ServerHostName if no x-forwarded-host header, # otherwise use the final hostname in x-forwarded-host. host = request.headers.getRawHeaders( "x-forwarded-host", [config.ServerHostName] )[-1].split(",")[-1].strip() port = 443 if (config.EnableSSL or config.BehindTLSProxy) else 80 scheme = "https" if config.EnableSSL else "http" response = RedirectResponse( request.unparseURL( host=host, port=port, scheme=scheme, path=config.WebCalendarAuthPath, querystring="redirect={}://{}{}".format( scheme, host, request.path ) ), temporary=True ) raise HTTPError(response) # We don't want the /inbox resource to pay attention to SACLs because # we just want it to use the hard-coded ACL for the imip reply user. # The /timezones resource is used by the wiki web calendar, so open # up that resource. if segments[0] in ("inbox", "timezones"): request.checkedSACL = True elif ( ( len(segments) > 2 and segments[0] in ("calendars", "principals") and ( segments[1] == "wikis" or ( segments[1] == "__uids__" and segments[2].startswith(WikiDirectoryService.uidPrefix) ) ) ) ): # This is a wiki-related calendar resource. SACLs are not checked. request.checkedSACL = True # The authzuser value is set to that of the wiki principal if # not already set. if not hasattr(request, "authzUser") and segments[2]: wikiUid = None if segments[1] == "wikis": wikiUid = "{}{}".format(WikiDirectoryService.uidPrefix, segments[2]) else: wikiUid = segments[2] if wikiUid: log.debug( "Wiki principal {name} being assigned to authzUser", name=wikiUid ) request.authzUser = yield self.principalForUID(request, wikiUid) elif ( self.useSacls and not hasattr(request, "checkedSACL") ): yield self.checkSACL(request) if config.RejectClients: # # Filter out unsupported clients # agent = request.headers.getHeader("user-agent") if agent is not None: for reject in config.RejectClients: if reject.search(agent) is not None: log.info("Rejecting user-agent: {agent}", agent=agent) raise HTTPError(StatusResponse( responsecode.FORBIDDEN, "Your client software ({}) is not allowed to " "access this service." .format(agent) )) if not hasattr(request, "authnUser"): try: authnUser, authzUser = yield self.authenticate(request) request.authnUser = authnUser request.authzUser = authzUser except (UnauthorizedLogin, LoginFailed): response = yield UnauthorizedResponse.makeResponse( request.credentialFactories, request.remoteAddr ) raise HTTPError(response) if ( config.EnableResponseCache and request.method == "PROPFIND" and not getattr(request, "notInCache", False) and len(segments) > 1 ): try: if not getattr(request, "checkingCache", False): request.checkingCache = True response = yield self.responseCache.getResponseForRequest( request ) if response is None: request.notInCache = True raise KeyError("Not found in cache.") returnValue((_CachedResponseResource(response), [])) except KeyError: pass child = yield super(RootResource, self).locateChild( request, segments ) returnValue(child)
def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query): """ Generate a calendar-query REPORT. (CalDAV-access-09, section 7.6) """ # Verify root element if calendar_query.qname() != (caldav_namespace, "calendar-query"): raise ValueError("{CalDAV:}calendar-query expected as root element, not %s." % (calendar_query.sname(),)) if not self.isCollection(): parent = (yield self.locateParent(request, request.uri)) if not parent.isPseudoCalendarCollection(): log.error("calendar-query report is not allowed on a resource outside of a calendar collection %s" % (self,)) raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be calendar collection or calendar resource")) responses = [] xmlfilter = calendar_query.filter filter = Filter(xmlfilter) props = calendar_query.props assert props is not None # Get the original timezone provided in the query, if any, and validate it now query_timezone = None query_tz = calendar_query.timezone if query_tz is not None and not query_tz.valid(): msg = "CalDAV:timezone must contain one VTIMEZONE component only: %s" % (query_tz,) log.error(msg) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), "Invalid calendar-data", )) if query_tz: filter.settimezone(query_tz) query_timezone = tuple(calendar_query.timezone.calendar().subcomponents())[0] if props.qname() == ("DAV:", "allprop"): propertiesForResource = report_common.allPropertiesForResource generate_calendar_data = False elif props.qname() == ("DAV:", "propname"): propertiesForResource = report_common.propertyNamesForResource generate_calendar_data = False elif props.qname() == ("DAV:", "prop"): propertiesForResource = report_common.propertyListForResource # Verify that any calendar-data element matches what we can handle result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(props) if not result: log.error(message) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data"), "Invalid calendar-data", )) else: raise AssertionError("We shouldn't be here") # Verify that the filter element is valid if (filter is None) or not filter.valid(): log.error("Invalid filter element: %r" % (xmlfilter,)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "valid-filter"), "Invalid filter element", )) matchcount = [0] max_number_of_results = [config.MaxQueryWithDataResults if generate_calendar_data else None, ] @inlineCallbacks def doQuery(calresource, uri): """ Run a query on the specified calendar collection accumulating the query responses. @param calresource: the L{CalDAVResource} for a calendar collection. @param uri: the uri for the calendar collection resource. """ @inlineCallbacks def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True): """ Run a query on the specified calendar. @param resource: the L{CalDAVResource} for the calendar. @param uri: the uri of the resource. @param name: the name of the resource. @param calendar: the L{Component} calendar read from the resource. """ # Handle private events access restrictions if not isowner: access = resource.accessMode else: access = None if query_ok or filter.match(calendar, access): # Check size of results is within limit matchcount[0] += 1 if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]: raise NumberOfMatchesWithinLimits(max_number_of_results[0]) if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) try: yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during query: %s" % (href,)) # Check whether supplied resource is a calendar or a calendar object resource if calresource.isPseudoCalendarCollection(): # Get the timezone property from the collection if one was not set in the query, # and store in the query filter for later use has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request)) timezone = query_timezone if query_tz is None and has_prop: tz = (yield calresource.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Do some optimization of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = (yield calresource.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield calresource.isOwner(request)) # Check for disabled access if filteredaces is not None: index_query_ok = True try: # Get list of children that match the search and have read access names = [name for name, ignore_uid, ignore_type in (yield calresource.search(filter))] except IndexedSearchException: names = yield calresource.listChildren() index_query_ok = False if not names: returnValue(True) # Now determine which valid resources are readable and which are not ok_resources = [] yield calresource.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), None, None, None, names, (davxml.Read(),), inherited_aces=filteredaces ) for child, child_uri in ok_resources: child_uri_name = child_uri[child_uri.rfind("/") + 1:] if generate_calendar_data or not index_query_ok: calendar = (yield child.iCalendarForUser(request)) assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self) else: calendar = None yield queryCalendarObjectResource(child, uri, child_uri_name, calendar, timezone, query_ok=index_query_ok, isowner=isowner) else: # Get the timezone property from the collection if one was not set in the query, # and store in the query object for later use timezone = query_timezone if query_tz is None: parent = (yield calresource.locateParent(request, uri)) assert parent is not None and parent.isPseudoCalendarCollection() has_prop = (yield parent.hasProperty(CalendarTimeZone(), request)) if has_prop: tz = (yield parent.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Check private events access status isowner = (yield calresource.isOwner(request)) calendar = (yield calresource.iCalendarForUser(request)) yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone) returnValue(True) # Run report taking depth into account try: depth = request.headers.getHeader("depth", "0") yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),)) except TooManyInstancesError, ex: log.error("Too many instances need to be computed in calendar-query report") raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, MaxInstances.fromString(str(ex.max_allowed)), "Too many instances", ))
# Run report taking depth into account try: depth = request.headers.getHeader("depth", "0") yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),)) except TooManyInstancesError, ex: log.error("Too many instances need to be computed in calendar-query report") raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, MaxInstances.fromString(str(ex.max_allowed)), "Too many instances", )) except NumberOfMatchesWithinLimits: log.error("Too many matching components in calendar-query report") raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits(), "Too many components", )) except TimeRangeLowerLimit, e: raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, caldavxml.MinDateTime(), "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),) )) except TimeRangeUpperLimit, e: raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, caldavxml.MaxDateTime(), "Time-range value too far in the future. Must be on or before %s." % (str(e.limit),) ))
def listChildren(self): # Not a listable collection raise HTTPError(responsecode.FORBIDDEN)
def createSimilarFile(self, path): log.error("Attempt to create clone %r of resource %r" % (path, self)) raise HTTPError(responsecode.NOT_FOUND)
# doc = waitForDeferred(davXMLFromStream(request.stream)) yield doc try: doc = doc.getResult() except ValueError, e: log.error("Error while handling ACL body: %s" % (e,)) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e))) # # Set properties # if doc is None: error = "Request XML body is required." log.error("Error: {err}", err=error) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error)) # # Parse request # acl = doc.root_element if not isinstance(acl, davxml.ACL): error = ("Request XML body must be an acl element." % (davxml.PropertyUpdate.sname(),)) log.error("Error: {err}", err=error) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error)) # # Do ACL merger # result = waitForDeferred(self.mergeAccessControlList(acl, request))