request.submethod = name try: method = getattr(self, method_name) # Also double-check via supported-reports property reports = self.supportedReports() test = lookupElement((namespace, name)) if not test: raise AttributeError() test = davxml.Report(test()) if test not in reports: raise AttributeError() except AttributeError: # # Requested report is not supported. # log.error("Unsupported REPORT %s for resource %s (no method %s)" % (encodeXMLName(namespace, name), self, method_name)) raise HTTPError( ErrorResponse(responsecode.FORBIDDEN, davxml.SupportedReport())) d = waitForDeferred(method(request, doc.root_element)) yield d yield d.getResult() http_REPORT = deferredGenerator(http_REPORT)
reports = self.supportedReports() test = lookupElement((namespace, name)) if not test: raise AttributeError() test = davxml.Report(test()) if test not in reports: raise AttributeError() except AttributeError: # # Requested report is not supported. # log.error("Unsupported REPORT {name} for resource {resource} (no method {method})", name=encodeXMLName(namespace, name), resource=self, method=method_name) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, davxml.SupportedReport(), "Report not supported on this resource", )) # # Check authentication and access controls # privileges = (davxml.Read(),) if method_name == "report_urn_ietf_params_xml_ns_caldav_free_busy_query": privileges = (caldavxml.ReadFreeBusy(),) yield self.authorize(request, privileges) result = (yield method(request, doc.root_element)) returnValue(result)
def report_DAV__sync_collection(self, request, sync_collection): """ Generate a sync-collection REPORT. """ # These resource support the report if not config.EnableSyncReport or element.Report(element.SyncCollection(),) not in self.supportedReports(): log.error("sync-collection report is only allowed on calendar/inbox/addressbook/notification collection resources {s!r}", s=self) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, element.SupportedReport(), "Report not supported on this resource", )) responses = [] # Do not support limit if sync_collection.sync_limit is not None: raise HTTPError(ErrorResponse( responsecode.INSUFFICIENT_STORAGE_SPACE, element.NumberOfMatchesWithinLimits(), "Report limit not supported", )) # Process Depth and sync-level for backwards compatibility # Use sync-level if present and ignore Depth, else use Depth if sync_collection.sync_level: depth = sync_collection.sync_level if depth == "infinite": depth = "infinity" descriptor = "DAV:sync-level" else: depth = request.headers.getHeader("depth", None) descriptor = "Depth header without DAV:sync-level" if depth not in ("1", "infinity"): log.error("sync-collection report with invalid depth header: {d}", d=depth) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid %s value" % (descriptor,))) propertyreq = sync_collection.property.children if sync_collection.property else None # 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 self.inheritedACEsforChildren(request)) changed, removed, notallowed, newtoken, resourceChanged = yield self.whatchanged(sync_collection.sync_token, depth) # Now determine which valid resources are readable and which are not ok_resources = [] forbidden_resources = [] if changed: yield self.findChildrenFaster( depth, request, lambda x, y: ok_resources.append((x, y)), lambda x, y: forbidden_resources.append((x, y)), None, None, changed, (element.Read(),), inherited_aces=filteredaces ) if resourceChanged: ok_resources.append((self, request.uri)) for child, child_uri in ok_resources: href = element.HRef.fromString(child_uri) try: if propertyreq: yield responseForHref( request, responses, href, child, functools.partial(_namedPropertiesForResource, dataAllowed=False, forbidden=False), propertyreq) else: responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.OK))) 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 sync: {h}", h=href) for child, child_uri in forbidden_resources: href = element.HRef.fromString(child_uri) try: if propertyreq: yield responseForHref( request, responses, href, child, functools.partial(_namedPropertiesForResource, dataAllowed=False, forbidden=True), propertyreq) else: responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.OK))) 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 sync: {h}", h=href) for name in removed: href = element.HRef.fromString(joinURL(request.uri, name)) responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_FOUND))) for name in notallowed: href = element.HRef.fromString(joinURL(request.uri, name)) responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_ALLOWED))) if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["responses"] = len(responses) responses.append(element.SyncToken.fromString(newtoken)) returnValue(MultiStatusResponse(responses))
def report_DAV__sync_collection(self, request, sync_collection): """ Generate a sync-collection REPORT. """ # These resource support the report if not config.EnableSyncReport or element.Report( element.SyncCollection(), ) not in self.supportedReports(): log.error( "sync-collection report is only allowed on calendar/inbox/addressbook/notification collection resources %s" % (self, )) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, element.SupportedReport(), "Report not supported on this resource", )) responses = [] # Do not support limit if sync_collection.sync_limit is not None: raise HTTPError( ErrorResponse( responsecode.INSUFFICIENT_STORAGE_SPACE, element.NumberOfMatchesWithinLimits(), "Report limit not supported", )) # Process Depth and sync-level for backwards compatibility # Use sync-level if present and ignore Depth, else use Depth if sync_collection.sync_level: depth = sync_collection.sync_level if depth == "infinite": depth = "infinity" descriptor = "DAV:sync-level" else: depth = request.headers.getHeader("depth", None) descriptor = "Depth header without DAV:sync-level" if depth not in ("1", "infinity"): log.error("sync-collection report with invalid depth header: %s" % (depth, )) raise HTTPError( StatusResponse(responsecode.BAD_REQUEST, "Invalid %s value" % (descriptor, ))) propertyreq = sync_collection.property.children if sync_collection.property else None @inlineCallbacks def _namedPropertiesForResource(request, props, resource, forbidden=False): """ Return the specified properties on the specified resource. @param request: the L{IRequest} for the current request. @param props: a list of property elements or qname tuples for the properties of interest. @param resource: the L{DAVResource} for the targeted resource. @return: a map of OK and NOT FOUND property values. """ properties_by_status = { responsecode.OK: [], responsecode.FORBIDDEN: [], responsecode.NOT_FOUND: [], } for property in props: if isinstance(property, element.WebDAVElement): qname = property.qname() else: qname = property if forbidden: properties_by_status[responsecode.FORBIDDEN].append( propertyName(qname)) else: props = (yield resource.listProperties(request)) if qname in props: try: prop = (yield resource.readProperty(qname, request)) properties_by_status[responsecode.OK].append(prop) except: f = Failure() log.error( "Error reading property %r for resource %s: %s" % (qname, request.uri, f.value)) status = statusForFailure( f, "getting property: %s" % (qname, )) if status not in properties_by_status: properties_by_status[status] = [] properties_by_status[status].append( propertyName(qname)) else: properties_by_status[responsecode.NOT_FOUND].append( propertyName(qname)) returnValue(properties_by_status) # 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 self.inheritedACEsforChildren(request)) changed, removed, notallowed, newtoken = yield self.whatchanged( sync_collection.sync_token, depth) # Now determine which valid resources are readable and which are not ok_resources = [] forbidden_resources = [] if changed: yield self.findChildrenFaster(depth, request, lambda x, y: ok_resources.append((x, y)), lambda x, y: forbidden_resources.append( (x, y)), None, None, changed, (element.Read(), ), inherited_aces=filteredaces) for child, child_uri in ok_resources: href = element.HRef.fromString(child_uri) try: yield responseForHref( request, responses, href, child, functools.partial(_namedPropertiesForResource, forbidden=False) if propertyreq else None, propertyreq) 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 sync: %s" % (href, )) for child, child_uri in forbidden_resources: href = element.HRef.fromString(child_uri) try: yield responseForHref( request, responses, href, child, functools.partial(_namedPropertiesForResource, forbidden=True) if propertyreq else None, propertyreq) 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 sync: %s" % (href, )) for name in removed: href = element.HRef.fromString(joinURL(request.uri, name)) responses.append( element.StatusResponse( element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_FOUND))) for name in notallowed: href = element.HRef.fromString(joinURL(request.uri, name)) responses.append( element.StatusResponse( element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_ALLOWED))) if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["responses"] = len(responses) responses.append(element.SyncToken.fromString(newtoken)) returnValue(MultiStatusResponse(responses))