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,))
def queryDirectoryBackedAddressBook(directoryBackedAddressBook, addressBookFilter): """ """ results, limited[ 0] = yield directoryBackedAddressBook.doAddressBookDirectoryQuery( addressBookFilter, query, max_number_of_results[0]) for vCardResult in results: # match against original filter if different from addressBookFilter if addressBookFilter is filter or filter.match( (yield vCardResult.vCard())): # Check size of results is within limit checkMaxResults() try: yield report_common.responseForHref( request, responses, vCardResult.hRef(), vCardResult, propertiesForResource, query, vcard=(yield vCardResult.vCard())) 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: {href}", href=vCardResult.hRef())
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: try: access = resource.readDeadProperty(TwistedCalendarAccessProperty) except HTTPError: access = None 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) return report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone) else: return succeed(None)
def queryAddressBookObjectResource(resource, uri, name, vcard, query_ok=False): """ Run a query on the specified vcard. @param resource: the L{CalDAVResource} for the vcard. @param uri: the uri of the resource. @param name: the name of the resource. @param vcard: the L{Component} vcard read from the resource. """ if query_ok or filter.match(vcard): # Check size of results is within limit checkMaxResults() 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, query, vcard=vcard) 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,))
def doDirectoryAddressBookResponse(): directoryAddressBookLock = None try: # Verify that requested resources are immediate children of the request-URI # and get vCardFilters ;similar to "normal" case below but do not call getChild() vCardFilters = [] valid_hrefs = [] for href in resources: resource_uri = str(href) resource_name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if self._isChildURI(request, resource_uri) and resource_name.endswith(".vcf") and len(resource_name) > 4: valid_hrefs.append(href) textMatchElement = carddavxml.TextMatch.fromString(resource_name[:-4]) textMatchElement.attributes["match-type"] = "equals" # do equals compare. Default is "contains" vCardFilters.append(carddavxml.PropertyFilter( textMatchElement, name="UID", # attributes )) else: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) # exit if not valid if not vCardFilters or not valid_hrefs: returnValue(None) addressBookFilter = carddavxml.Filter(*vCardFilters) addressBookFilter = addressbookqueryfilter.Filter(addressBookFilter) #get vCards and filter limit = config.DirectoryAddressBook.MaxQueryResults results, limited = (yield self.directory.doAddressBookQuery(addressBookFilter, propertyreq, limit)) if limited: log.err("Too many results in multiget report: %d" % len(resources)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (dav_namespace, "number-of-matches-within-limits"), "Too many results", )) for href in valid_hrefs: matchingResource = None for vCardResource in results: if href == vCardResource.hRef(): # might need to compare urls instead - also case sens ok? matchingResource = vCardResource break; if matchingResource: yield report_common.responseForHref(request, responses, href, matchingResource, propertiesForResource, propertyreq, vcard=matchingResource.vCard()) else: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) finally: if directoryAddressBookLock: yield directoryAddressBookLock.release()
def doDirectoryAddressBookResponse(): directoryAddressBookLock = None try: # Verify that requested resources are immediate children of the request-URI # and get vCardFilters ;similar to "normal" case below but do not call getChild() vCardFilters = [] valid_hrefs = [] for href in resources: resource_uri = str(href) resource_name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if self._isChildURI(request, resource_uri) and resource_name.endswith(".vcf") and len(resource_name) > 4: valid_hrefs.append(href) textMatchElement = carddavxml.TextMatch.fromString(resource_name[:-4]) textMatchElement.attributes["match-type"] = "equals" # do equals compare. Default is "contains" vCardFilters.append(carddavxml.PropertyFilter( textMatchElement, name="UID", # attributes )) else: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) # exit if not valid if not vCardFilters or not valid_hrefs: returnValue(None) addressBookFilter = carddavxml.Filter(*vCardFilters) addressBookFilter = Filter(addressBookFilter) # get vCards and filter limit = config.DirectoryAddressBook.MaxQueryResults results, limited = (yield self.doAddressBookDirectoryQuery(addressBookFilter, propertyreq, limit, defaultKind=None)) if limited: log.error("Too many results in multiget report: {count}", count=len(resources)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (dav_namespace, "number-of-matches-within-limits"), "Too many results", )) for href in valid_hrefs: matchingResource = None for vCardResource in results: if href == vCardResource.hRef(): # might need to compare urls instead - also case sens ok? matchingResource = vCardResource break if matchingResource: yield report_common.responseForHref(request, responses, href, matchingResource, propertiesForResource, propertyreq, vcard=matchingResource.vCard()) else: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) finally: if directoryAddressBookLock: yield directoryAddressBookLock.release()
def queryDirectoryBackedAddressBook(directoryBackedAddressBook, addressBookFilter): """ """ records, limited[0] = (yield directoryBackedAddressBook.directory.vCardRecordsForAddressBookQuery( addressBookFilter, query, max_number_of_results[0] )) for vCardRecord in records: # match against original filter if filter.match(vCardRecord.vCard()): # Check size of results is within limit checkMaxResults() yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=vCardRecord.vCard())
def queryAddressBookObjectResource(resource, uri, name, vcard, query_ok = False): """ Run a query on the specified vcard. @param resource: the L{CalDAVFile} for the vcard. @param uri: the uri of the resource. @param name: the name of the resource. @param vcard: the L{Component} vcard read from the resource. """ if query_ok or filter.match(vcard): # Check size of results is within limit checkMaxResults() if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) return report_common.responseForHref(request, responses, href, resource, propertiesForResource, query, vcard=vcard) else: return succeed(None)
def queryDirectoryBackedAddressBook(directoryBackedAddressBook, addressBookFilter): """ """ records, limited[0] = (yield directoryBackedAddressBook.directory.vCardRecordsForAddressBookQuery(addressBookFilter, query, max_number_of_results[0])) for vCardRecord in records: # match against original filter if filter.match((yield vCardRecord.vCard())): # Check size of results is within limit checkMaxResults() try: yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=(yield vCardRecord.vCard())) 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" % (vCardRecord.hRef(),))
def queryAddressBookObjectResource(resource, uri, name, vcard, query_ok=False): """ Run a query on the specified vcard. @param resource: the L{CalDAVResource} for the vcard. @param uri: the uri of the resource. @param name: the name of the resource. @param vcard: the L{Component} vcard read from the resource. """ if query_ok or filter.match(vcard): # Check size of results is within limit checkMaxResults() 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, query, vcard=vcard) 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: {href}", href=href)
def multiget_common(self, request, multiget, collection_type): """ Generate a multiget REPORT. """ # Make sure target resource is of the right type if not self.isCollection(): parent = (yield self.locateParent(request, request.uri)) if collection_type == COLLECTION_TYPE_CALENDAR: if not parent.isPseudoCalendarCollection(): log.error("calendar-multiget report is not allowed on a resource outside of a calendar collection %s" % (self,)) raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be calendar resource")) elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: if not parent.isAddressBookCollection(): log.error("addressbook-multiget report is not allowed on a resource outside of an address book collection %s" % (self,)) raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be address book resource")) responses = [] propertyreq = multiget.property resources = multiget.resources if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["rcount"] = len(resources) hasData = False if propertyreq.qname() == ("DAV:", "allprop"): propertiesForResource = report_common.allPropertiesForResource elif propertyreq.qname() == ("DAV:", "propname"): propertiesForResource = report_common.propertyNamesForResource elif propertyreq.qname() == ("DAV:", "prop"): propertiesForResource = report_common.propertyListForResource if collection_type == COLLECTION_TYPE_CALENDAR: # Verify that any calendar-data element matches what we can handle result, message, hasData = report_common.validPropertyListCalendarDataTypeVersion(propertyreq) precondition = (caldav_namespace, "supported-calendar-data") elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: # Verify that any address-data element matches what we can handle result, message, hasData = report_common.validPropertyListAddressDataTypeVersion(propertyreq) precondition = (carddav_namespace, "supported-address-data") else: result = True if not result: log.error(message) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, precondition, "Invalid object data element", )) else: raise AssertionError("We shouldn't be here") # Check size of results is within limit when data property requested if hasData and len(resources) > config.MaxMultigetWithDataHrefs: log.error("Too many results in multiget report returning data: %d" % len(resources)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits(), "Too many resources", )) """ Three possibilities exist: 1. The request-uri is a calendar collection, in which case all the hrefs MUST be one-level below that collection and must be calendar object resources. 2. The request-uri is a regular collection, in which case all the hrefs MUST be children of that (at any depth) but MUST also be calendar object resources (i.e. immediate child of a calendar collection). 3. The request-uri is a resource, in which case there MUST be a single href equal to the request-uri, and MUST be a calendar object resource. """ disabled = False if self.isPseudoCalendarCollection(): requestURIis = "calendar" # Do some optimisation 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)) # Check for disabled access if filteredaces is None: disabled = True # Check private events access status isowner = (yield self.isOwner(request)) elif self.isAddressBookCollection(): requestURIis = "addressbook" # Do some optimisation 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)) # Check for disabled access if filteredaces is None: disabled = True isowner = None elif self.isCollection(): requestURIis = "collection" filteredaces = None lastParent = None isowner = None else: requestURIis = "resource" filteredaces = None isowner = None if not disabled: @inlineCallbacks def doResponse(): # Special for addressbooks if collection_type == COLLECTION_TYPE_ADDRESSBOOK: if self.isDirectoryBackedAddressBookCollection() and self.directory.liveQuery: result = (yield doDirectoryAddressBookResponse()) returnValue(result) # Verify that requested resources are immediate children of the request-URI valid_names = [] for href in resources: resource_uri = str(href) name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if not self._isChildURI(request, resource_uri): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.BAD_REQUEST))) else: valid_names.append(name) if not valid_names: returnValue(None) # Now determine which valid resources are readable and which are not ok_resources = [] bad_resources = [] missing_resources = [] unavailable_resources = [] yield self.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), lambda x, y: bad_resources.append((x, y)), lambda x: missing_resources.append(x), lambda x: unavailable_resources.append(x), valid_names, (davxml.Read(),), inherited_aces=filteredaces ) # Get properties for all valid readable resources for resource, href in ok_resources: try: yield report_common.responseForHref( request, responses, davxml.HRef.fromString(href), resource, propertiesForResource, propertyreq, isowner=isowner ) except ValueError: log.error("Invalid calendar resource during multiget: %s" % (href,)) responses.append(davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) 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, return a 404 for the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during multiget: %s" % (href,)) responses.append(davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.NOT_FOUND) )) # Indicate error for all valid non-readable resources for ignore_resource, href in bad_resources: responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) # Indicate error for all missing/unavailable resources for href in missing_resources: responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) for href in unavailable_resources: responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.SERVICE_UNAVAILABLE))) @inlineCallbacks def doDirectoryAddressBookResponse(): directoryAddressBookLock = None try: # Verify that requested resources are immediate children of the request-URI # and get vCardFilters ;similar to "normal" case below but do not call getChild() vCardFilters = [] valid_hrefs = [] for href in resources: resource_uri = str(href) resource_name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if self._isChildURI(request, resource_uri) and resource_name.endswith(".vcf") and len(resource_name) > 4: valid_hrefs.append(href) vCardFilters.append(carddavxml.PropertyFilter( carddavxml.TextMatch.fromString(resource_name[:-4]), name="UID", # attributes )) elif not self.directory.cacheQuery: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) # exit if not valid if not vCardFilters or not valid_hrefs: returnValue(None) addressBookFilter = carddavxml.Filter(*vCardFilters) addressBookFilter = addressbookqueryfilter.Filter(addressBookFilter) if self.directory.cacheQuery: # add vcards to directory address book and run "normal case" below limit = config.DirectoryAddressBook.MaxQueryResults directoryAddressBookLock, limited = (yield self.directory.cacheVCardsForAddressBookQuery(addressBookFilter, propertyreq, limit)) if limited: log.error("Too many results in multiget report: %d" % len(resources)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (dav_namespace, "number-of-matches-within-limits"), "Too many results", )) else: #get vCards and filter limit = config.DirectoryAddressBook.MaxQueryResults vCardRecords, limited = (yield self.directory.vCardRecordsForAddressBookQuery(addressBookFilter, propertyreq, limit)) if limited: log.error("Too many results in multiget report: %d" % len(resources)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (dav_namespace, "number-of-matches-within-limits"), "Too many results", )) for href in valid_hrefs: matchingRecord = None for vCardRecord in vCardRecords: if href == vCardRecord.hRef(): # might need to compare urls instead - also case sens ok? matchingRecord = vCardRecord break if matchingRecord: yield report_common.responseForHref(request, responses, href, matchingRecord, propertiesForResource, propertyreq, vcard=matchingRecord.vCard()) else: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) finally: if directoryAddressBookLock: yield directoryAddressBookLock.release() if requestURIis == "calendar" or requestURIis == "addressbook": yield doResponse() else: for href in resources: resource_uri = str(href) # Do href checks if requestURIis == "calendar": pass elif requestURIis == "addressbook": pass # TODO: we can optimize this one in a similar manner to the calendar case elif requestURIis == "collection": name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if not self._isChildURI(request, resource_uri, False): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) continue child = (yield request.locateResource(resource_uri)) if not child or not child.exists(): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) continue parent = (yield child.locateParent(request, resource_uri)) if collection_type == COLLECTION_TYPE_CALENDAR: if not parent.isCalendarCollection() or not (yield parent.index().resourceExists(name)): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) continue elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) continue # Check privileges on parent - must have at least DAV:read try: yield parent.checkPrivileges(request, (davxml.Read(),)) except AccessDeniedError: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) continue # Cache the last parent's inherited aces for checkPrivileges optimization if lastParent != parent: lastParent = parent # Do some optimisation 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 parent.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield parent.isOwner(request)) else: name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if (resource_uri != request.uri) or not self.exists(): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) continue parent = (yield self.locateParent(request, resource_uri)) if collection_type == COLLECTION_TYPE_CALENDAR: if not parent.isPseudoCalendarCollection() or not (yield parent.index().resourceExists(name)): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) continue elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) continue child = self # Do some optimisation 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 parent.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield parent.isOwner(request)) # Check privileges - must have at least DAV:read try: yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces) except AccessDeniedError: responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) continue yield report_common.responseForHref(request, responses, href, child, propertiesForResource, propertyreq, isowner=isowner) returnValue(MultiStatusResponse(responses))
def doResponse(): # Special for addressbooks if collection_type == COLLECTION_TYPE_ADDRESSBOOK: if self.isDirectoryBackedAddressBookCollection() and self.directory.liveQuery: result = (yield doDirectoryAddressBookResponse()) returnValue(result) # Verify that requested resources are immediate children of the request-URI valid_names = [] for href in resources: resource_uri = str(href) name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if not self._isChildURI(request, resource_uri): responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.BAD_REQUEST))) else: valid_names.append(name) if not valid_names: returnValue(None) # Now determine which valid resources are readable and which are not ok_resources = [] bad_resources = [] missing_resources = [] unavailable_resources = [] yield self.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), lambda x, y: bad_resources.append((x, y)), lambda x: missing_resources.append(x), lambda x: unavailable_resources.append(x), valid_names, (davxml.Read(),), inherited_aces=filteredaces ) # Get properties for all valid readable resources for resource, href in ok_resources: try: yield report_common.responseForHref( request, responses, davxml.HRef.fromString(href), resource, propertiesForResource, propertyreq, isowner=isowner ) except ValueError: log.error("Invalid calendar resource during multiget: %s" % (href,)) responses.append(davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) 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, return a 404 for the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during multiget: %s" % (href,)) responses.append(davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.NOT_FOUND) )) # Indicate error for all valid non-readable resources for ignore_resource, href in bad_resources: responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))) # Indicate error for all missing/unavailable resources for href in missing_resources: responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.NOT_FOUND))) for href in unavailable_resources: responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.SERVICE_UNAVAILABLE)))
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 # 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: %s" % (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: %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))
def multiget_common(self, request, multiget, collection_type): """ Generate a multiget REPORT. """ # Make sure target resource is of the right type if not self.isCollection(): parent = (yield self.locateParent(request, request.uri)) if collection_type == COLLECTION_TYPE_CALENDAR: if not parent.isPseudoCalendarCollection(): log.error( "calendar-multiget report is not allowed on a resource outside of a calendar collection {res}", res=self) raise HTTPError( StatusResponse(responsecode.FORBIDDEN, "Must be calendar resource")) elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: if not parent.isAddressBookCollection(): log.error( "addressbook-multiget report is not allowed on a resource outside of an address book collection {res}", res=self) raise HTTPError( StatusResponse(responsecode.FORBIDDEN, "Must be address book resource")) responses = [] propertyreq = multiget.property resources = multiget.resources if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["rcount"] = len(resources) hasData = False if propertyreq.qname() == ("DAV:", "allprop"): propertiesForResource = report_common.allPropertiesForResource elif propertyreq.qname() == ("DAV:", "propname"): propertiesForResource = report_common.propertyNamesForResource elif propertyreq.qname() == ("DAV:", "prop"): propertiesForResource = report_common.propertyListForResource if collection_type == COLLECTION_TYPE_CALENDAR: # Verify that any calendar-data element matches what we can handle result, message, hasData = report_common.validPropertyListCalendarDataTypeVersion( propertyreq) precondition = (caldav_namespace, "supported-calendar-data") elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: # Verify that any address-data element matches what we can handle result, message, hasData = report_common.validPropertyListAddressDataTypeVersion( propertyreq) precondition = (carddav_namespace, "supported-address-data") else: result = True if not result: log.error(message) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, precondition, "Invalid object data element", )) else: raise AssertionError("We shouldn't be here") # Check size of results is within limit when data property requested if hasData and len(resources) > config.MaxMultigetWithDataHrefs: log.error("Too many resources in multiget report: {count}", count=len(resources)) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits(), "Too many resources", )) """ Three possibilities exist: 1. The request-uri is a calendar collection, in which case all the hrefs MUST be one-level below that collection and must be calendar object resources. 2. The request-uri is a regular collection, in which case all the hrefs MUST be children of that (at any depth) but MUST also be calendar object resources (i.e. immediate child of a calendar collection). 3. The request-uri is a resource, in which case there MUST be a single href equal to the request-uri, and MUST be a calendar object resource. """ disabled = False if self.isPseudoCalendarCollection(): requestURIis = "calendar" # Do some optimisation 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)) # Check for disabled access if filteredaces is None: disabled = True # Check private events access status isowner = (yield self.isOwner(request)) elif self.isAddressBookCollection(): requestURIis = "addressbook" # Do some optimisation 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)) # Check for disabled access if filteredaces is None: disabled = True isowner = None elif self.isCollection(): requestURIis = "collection" filteredaces = None lastParent = None isowner = None else: requestURIis = "resource" filteredaces = None isowner = None if not disabled: @inlineCallbacks def doResponse(): # Special for addressbooks if collection_type == COLLECTION_TYPE_ADDRESSBOOK: if self.isDirectoryBackedAddressBookCollection(): result = (yield doDirectoryAddressBookResponse()) returnValue(result) # Verify that requested resources are immediate children of the request-URI valid_names = [] for href in resources: resource_uri = str(href) name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if not self._isChildURI(request, resource_uri): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.BAD_REQUEST))) else: valid_names.append(name) if not valid_names: returnValue(None) # Now determine which valid resources are readable and which are not ok_resources = [] bad_resources = [] missing_resources = [] unavailable_resources = [] yield self.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), lambda x, y: bad_resources.append((x, y)), lambda x: missing_resources.append(x), lambda x: unavailable_resources.append(x), valid_names, (davxml.Read(), ), inherited_aces=filteredaces) # Get properties for all valid readable resources for resource, href in ok_resources: try: yield report_common.responseForHref( request, responses, davxml.HRef.fromString(href), resource, propertiesForResource, propertyreq, isowner=isowner) except ValueError: log.error( "Invalid calendar resource during multiget: {href}", href=href) responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) 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, return a 404 for the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during multiget: {href}", href=href) responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) # Indicate error for all valid non-readable resources for ignore_resource, href in bad_resources: responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) # Indicate error for all missing/unavailable resources for href in missing_resources: responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) for href in unavailable_resources: responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.SERVICE_UNAVAILABLE))) @inlineCallbacks def doDirectoryAddressBookResponse(): directoryAddressBookLock = None try: # Verify that requested resources are immediate children of the request-URI # and get vCardFilters ;similar to "normal" case below but do not call getChild() vCardFilters = [] valid_hrefs = [] for href in resources: resource_uri = str(href) resource_name = unquote( resource_uri[resource_uri.rfind("/") + 1:]) if self._isChildURI( request, resource_uri) and resource_name.endswith( ".vcf") and len(resource_name) > 4: valid_hrefs.append(href) textMatchElement = carddavxml.TextMatch.fromString( resource_name[:-4]) textMatchElement.attributes[ "match-type"] = "equals" # do equals compare. Default is "contains" vCardFilters.append( carddavxml.PropertyFilter( textMatchElement, name="UID", # attributes )) else: responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) # exit if not valid if not vCardFilters or not valid_hrefs: returnValue(None) addressBookFilter = carddavxml.Filter(*vCardFilters) addressBookFilter = Filter(addressBookFilter) # get vCards and filter limit = config.DirectoryAddressBook.MaxQueryResults results, limited = (yield self.doAddressBookDirectoryQuery( addressBookFilter, propertyreq, limit, defaultKind=None)) if limited: log.error("Too many results in multiget report: {count}", count=len(resources)) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (dav_namespace, "number-of-matches-within-limits"), "Too many results", )) for href in valid_hrefs: matchingResource = None for vCardResource in results: if href == vCardResource.hRef( ): # might need to compare urls instead - also case sens ok? matchingResource = vCardResource break if matchingResource: yield report_common.responseForHref( request, responses, href, matchingResource, propertiesForResource, propertyreq, vcard=matchingResource.vCard()) else: responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) finally: if directoryAddressBookLock: yield directoryAddressBookLock.release() if requestURIis == "calendar" or requestURIis == "addressbook": yield doResponse() else: for href in resources: resource_uri = str(href) # Do href checks if requestURIis == "calendar": pass elif requestURIis == "addressbook": pass # TODO: we can optimize this one in a similar manner to the calendar case elif requestURIis == "collection": name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if not self._isChildURI(request, resource_uri, False): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) continue child = (yield request.locateResource(resource_uri)) if not child or not child.exists(): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) continue parent = (yield child.locateParent(request, resource_uri)) if collection_type == COLLECTION_TYPE_CALENDAR: if not parent.isCalendarCollection() or not ( yield parent.resourceExists(name)): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) continue elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: if not parent.isAddressBookCollection() or not ( yield parent.resourceExists(name)): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) continue # Check privileges on parent - must have at least DAV:read try: yield parent.checkPrivileges(request, (davxml.Read(), )) except AccessDeniedError: responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) continue # Cache the last parent's inherited aces for checkPrivileges optimization if lastParent != parent: lastParent = parent # Do some optimisation 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 parent.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield parent.isOwner(request)) else: name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if (resource_uri != request.uri) or not self.exists(): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) continue parent = (yield self.locateParent(request, resource_uri)) if collection_type == COLLECTION_TYPE_CALENDAR: if not parent.isPseudoCalendarCollection() or not ( yield parent.resourceExists(name)): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) continue elif collection_type == COLLECTION_TYPE_ADDRESSBOOK: if not parent.isAddressBookCollection() or not ( yield parent.resourceExists(name)): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) continue child = self # Do some optimisation 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 parent.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield parent.isOwner(request)) # Check privileges - must have at least DAV:read try: yield child.checkPrivileges(request, (davxml.Read(), ), inherited_aces=filteredaces) except AccessDeniedError: responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) continue yield report_common.responseForHref(request, responses, href, child, propertiesForResource, propertyreq, isowner=isowner) returnValue(MultiStatusResponse(responses))
def doResponse(): # Special for addressbooks if collection_type == COLLECTION_TYPE_ADDRESSBOOK: if self.isDirectoryBackedAddressBookCollection(): result = (yield doDirectoryAddressBookResponse()) returnValue(result) # Verify that requested resources are immediate children of the request-URI valid_names = [] for href in resources: resource_uri = str(href) name = unquote(resource_uri[resource_uri.rfind("/") + 1:]) if not self._isChildURI(request, resource_uri): responses.append( davxml.StatusResponse( href, davxml.Status.fromResponseCode( responsecode.BAD_REQUEST))) else: valid_names.append(name) if not valid_names: returnValue(None) # Now determine which valid resources are readable and which are not ok_resources = [] bad_resources = [] missing_resources = [] unavailable_resources = [] yield self.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), lambda x, y: bad_resources.append((x, y)), lambda x: missing_resources.append(x), lambda x: unavailable_resources.append(x), valid_names, (davxml.Read(), ), inherited_aces=filteredaces) # Get properties for all valid readable resources for resource, href in ok_resources: try: yield report_common.responseForHref( request, responses, davxml.HRef.fromString(href), resource, propertiesForResource, propertyreq, isowner=isowner) except ValueError: log.error( "Invalid calendar resource during multiget: {href}", href=href) responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) 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, return a 404 for the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during multiget: {href}", href=href) responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) # Indicate error for all valid non-readable resources for ignore_resource, href in bad_resources: responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) # Indicate error for all missing/unavailable resources for href in missing_resources: responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) for href in unavailable_resources: responses.append( davxml.StatusResponse( davxml.HRef.fromString(href), davxml.Status.fromResponseCode( responsecode.SERVICE_UNAVAILABLE)))