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()
Beispiel #2
0
def responseForHref(request, responses, href, resource, propertiesForResource,
                    propertyreq):

    if propertiesForResource is not None:
        properties_by_status = waitForDeferred(
            propertiesForResource(request, propertyreq, resource))
        yield properties_by_status
        properties_by_status = properties_by_status.getResult()

        propstats = []

        for status in properties_by_status:
            properties = properties_by_status[status]
            if properties:
                xml_status = element.Status.fromResponseCode(status)
                xml_container = element.PropertyContainer(*properties)
                xml_propstat = element.PropertyStatus(xml_container,
                                                      xml_status)

                propstats.append(xml_propstat)

        if propstats:
            responses.append(element.PropertyStatusResponse(href, *propstats))

    else:
        responses.append(
            element.StatusResponse(
                href,
                element.Status.fromResponseCode(responsecode.OK),
            ))
Beispiel #3
0
 def report_urn_ietf_params_xml_ns_caldav_calendar_multiget(
         self, request, multiget):
     responses = [
         davxml.StatusResponse(
             href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND))
         for href in multiget.resources
     ]
     return succeed(MultiStatusResponse((responses)))
Beispiel #4
0
 def response(self):
     """
     Generate a response from the responses contained in the queue or, if
     there are no such responses, return the C{success_response} provided to
     L{__init__}.
     @return: a L{element.PropertyStatusResponse}.
     """
     if self.propstats:
         return element.PropertyStatusResponse(element.HRef(self.uri),
                                               *self.propstats)
     else:
         return element.StatusResponse(
             element.HRef(self.uri),
             element.Status.fromResponseCode(self.success_response))
Beispiel #5
0
    def add(self, path, what):
        """
        Add a response.
        @param path: a path, which must be a subpath of C{path_basename} as
            provided to L{__init__}.
        @param what: a status code or a L{Failure} for the given path.
        """
        assert path.startswith(
            self.path_basename), "%s does not start with %s" % (
                path, self.path_basename)

        if type(what) is int:
            code = what
            error = None
            message = responsecode.RESPONSES[code]
        elif isinstance(what, Failure):
            code = statusForFailure(what)
            error = errorForFailure(what)
            message = messageForFailure(what)
        else:
            raise AssertionError("Unknown data type: %r" % (what, ))

        if code > 400:  # Error codes only
            log.error("Error during {method} for {path}: {msg}",
                      method=self.method,
                      path=path,
                      msg=message)

        uri = path[self.path_basename_len:]

        children = []
        children.append(element.HRef(uri))
        children.append(element.Status.fromResponseCode(code))
        if error is not None:
            children.append(error)
        if message is not None:
            children.append(element.ResponseDescription(message))
        self.responses.append(element.StatusResponse(*children))
def report_DAV__acl_principal_prop_set(self, request, acl_prinicpal_prop_set):
    """
    Generate an acl-prinicpal-prop-set REPORT. (RFC 3744, section 9.2)
    """
    # Verify root element
    if not isinstance(acl_prinicpal_prop_set, davxml.ACLPrincipalPropSet):
        raise ValueError("%s expected as root element, not %s." %
                         (davxml.ACLPrincipalPropSet.sname(),
                          acl_prinicpal_prop_set.sname()))

    # Depth must be "0"
    depth = request.headers.getHeader("depth", "0")
    if depth != "0":
        log.error("Error in prinicpal-prop-set REPORT, Depth set to %s" %
                  (depth, ))
        raise HTTPError(
            StatusResponse(responsecode.BAD_REQUEST,
                           "Depth %s not allowed" % (depth, )))

    #
    # Check authentication and access controls
    #
    x = waitForDeferred(self.authorize(request, (davxml.ReadACL(), )))
    yield x
    x.getResult()

    # Get a single DAV:prop element from the REPORT request body
    propertiesForResource = None
    propElement = None
    for child in acl_prinicpal_prop_set.children:
        if child.qname() == ("DAV:", "prop"):
            if propertiesForResource is not None:
                log.error("Only one DAV:prop element allowed")
                raise HTTPError(
                    StatusResponse(responsecode.BAD_REQUEST,
                                   "Only one DAV:prop element allowed"))
            propertiesForResource = prop_common.propertyListForResource
            propElement = child

    if propertiesForResource is None:
        log.error(
            "Error in acl-principal-prop-set REPORT, no DAV:prop element")
        raise HTTPError(
            StatusResponse(responsecode.BAD_REQUEST, "No DAV:prop element"))

    # Enumerate principals on ACL in current resource
    principals = []

    acl = waitForDeferred(self.accessControlList(request))
    yield acl
    acl = acl.getResult()

    for ace in acl.children:
        resolved = waitForDeferred(
            self.resolvePrincipal(ace.principal.children[0], request))
        yield resolved
        resolved = resolved.getResult()
        if resolved is not None and resolved not in principals:
            principals.append(resolved)

    # Run report for each referenced principal
    try:
        responses = []
        matchcount = 0
        for principal in principals:
            # Check size of results is within limit
            matchcount += 1
            if matchcount > max_number_of_matches:
                raise NumberOfMatchesWithinLimits(max_number_of_matches)

            resource = waitForDeferred(request.locateResource(str(principal)))
            yield resource
            resource = resource.getResult()

            if resource is not None:
                #
                # Check authentication and access controls
                #
                x = waitForDeferred(
                    resource.authorize(request, (davxml.Read(), )))
                yield x
                try:
                    x.getResult()
                except HTTPError:
                    responses.append(
                        davxml.StatusResponse(
                            principal,
                            davxml.Status.fromResponseCode(
                                responsecode.FORBIDDEN)))
                else:
                    d = waitForDeferred(
                        prop_common.responseForHref(request, responses,
                                                    principal, resource,
                                                    propertiesForResource,
                                                    propElement))
                    yield d
                    d.getResult()
            else:
                log.error("Requested principal resource not found: %s" %
                          (str(principal), ))
                responses.append(
                    davxml.StatusResponse(
                        principal,
                        davxml.Status.fromResponseCode(
                            responsecode.NOT_FOUND)))

    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components")
        raise HTTPError(
            ErrorResponse(responsecode.FORBIDDEN,
                          davxml.NumberOfMatchesWithinLimits()))

    yield MultiStatusResponse(responses)
class DirectoryPrincipalPropertySearchMixIn(object):
    @inlineCallbacks
    def report_DAV__principal_property_search(self, request,
                                              principal_property_search):
        """
        Generate a principal-property-search REPORT. (RFC 3744, section 9.4)
        Overrides twisted implementation, targeting only directory-enabled
        searching.
        """
        # Verify root element
        if not isinstance(principal_property_search,
                          element.PrincipalPropertySearch):
            msg = "%s expected as root element, not %s." % (
                element.PrincipalPropertySearch.sname(),
                principal_property_search.sname())
            log.warn(msg)
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

        # Should we AND (the default) or OR (if test="anyof")?
        testMode = principal_property_search.attributes.get("test", "allof")
        if testMode not in ("allof", "anyof"):
            msg = "Bad XML: unknown value for test attribute: %s" % (
                testMode, )
            log.warn(msg)
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
        operand = Operand.AND if testMode == "allof" else Operand.OR

        # Are we narrowing results down to a single CUTYPE?
        cuType = principal_property_search.attributes.get("type", None)
        if cuType not in ("INDIVIDUAL", "GROUP", "RESOURCE", "ROOM", None):
            msg = "Bad XML: unknown value for type attribute: %s" % (cuType, )
            log.warn(msg)
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

        # Only handle Depth: 0
        depth = request.headers.getHeader("depth", "0")
        if depth != "0":
            log.error(
                "Error in principal-property-search REPORT, Depth set to %s" %
                (depth, ))
            raise HTTPError(
                StatusResponse(responsecode.BAD_REQUEST,
                               "Depth %s not allowed" % (depth, )))

        # Get any limit value from xml
        clientLimit = None

        # Get a single DAV:prop element from the REPORT request body
        propertiesForResource = None
        propElement = None
        propertySearches = []
        applyTo = False
        for child in principal_property_search.children:
            if child.qname() == (dav_namespace, "prop"):
                propertiesForResource = prop_common.propertyListForResource
                propElement = child

            elif child.qname() == (dav_namespace,
                                   "apply-to-principal-collection-set"):
                applyTo = True

            elif child.qname() == (dav_namespace, "property-search"):
                props = child.childOfType(element.PropertyContainer)
                props.removeWhitespaceNodes()

                match = child.childOfType(element.Match)
                caseless = match.attributes.get("caseless", "yes")
                if caseless not in ("yes", "no"):
                    msg = "Bad XML: unknown value for caseless attribute: %s" % (
                        caseless, )
                    log.warn(msg)
                    raise HTTPError(
                        StatusResponse(responsecode.BAD_REQUEST, msg))
                caseless = (caseless == "yes")
                matchType = match.attributes.get("match-type",
                                                 u"contains").encode("utf-8")
                if matchType not in ("starts-with", "contains", "equals"):
                    msg = "Bad XML: unknown value for match-type attribute: %s" % (
                        matchType, )
                    log.warn(msg)
                    raise HTTPError(
                        StatusResponse(responsecode.BAD_REQUEST, msg))

                # Convert to twext.who.expression form
                matchType = {
                    "starts-with": MatchType.startsWith,
                    "contains": MatchType.contains,
                    "equals": MatchType.equals
                }.get(matchType)
                matchFlags = MatchFlags.caseInsensitive if caseless else MatchFlags.none

                # Ignore any query strings under three letters
                matchText = match.toString()  # gives us unicode
                if len(matchText) >= 3:
                    propertySearches.append(
                        (props.children, matchText, matchFlags, matchType))

            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))

        # Run report
        resultsWereLimited = None
        resources = []
        if applyTo or not hasattr(self, "directory"):
            for principalCollection in self.principalCollections():
                uri = principalCollection.principalCollectionURL()
                resource = (yield request.locateResource(uri))
                if resource:
                    resources.append((resource, uri))
        else:
            resources.append((self, request.uri))

        # We need to access a directory service
        principalCollection = resources[0][0]
        if not hasattr(principalCollection, "directory"):
            # Use Twisted's implementation instead in this case
            result = (yield super(DirectoryPrincipalPropertySearchMixIn,
                                  self).report_DAV__principal_property_search(
                                      request, principal_property_search))
            returnValue(result)

        dir = principalCollection.directory

        # See if we can take advantage of the directory
        fields = []
        nonDirectorySearches = []
        for props, match, matchFlags, matchType in propertySearches:
            nonDirectoryProps = []
            for prop in props:
                try:
                    fieldName, match = principalCollection.propertyToField(
                        prop, match)
                except ValueError, e:
                    raise HTTPError(
                        StatusResponse(responsecode.BAD_REQUEST, str(e)))
                if fieldName:
                    fields.append((fieldName, match, matchFlags, matchType))
                else:
                    nonDirectoryProps.append(prop)
            if nonDirectoryProps:
                nonDirectorySearches.append(
                    (nonDirectoryProps, match, matchFlags, matchType))

        matchingResources = []
        matchcount = 0

        # nonDirectorySearches are ignored
        if fields:

            records = (yield
                       dir.recordsMatchingFieldsWithCUType(fields,
                                                           operand=operand,
                                                           cuType=cuType))

            for record in records:
                resource = yield principalCollection.principalForRecord(record)
                if resource:
                    matchingResources.append(resource)

                    # We've determined this is a matching resource
                    matchcount += 1
                    if clientLimit is not None and matchcount >= clientLimit:
                        resultsWereLimited = ("client", matchcount)
                        break
                    if matchcount >= config.MaxPrincipalSearchReportResults:
                        resultsWereLimited = ("server", matchcount)
                        break

        # Generate the response
        responses = []
        for resource in matchingResources:
            url = resource.url()
            yield prop_common.responseForHref(request, responses,
                                              element.HRef.fromString(url),
                                              resource, propertiesForResource,
                                              propElement)

        if resultsWereLimited is not None:
            if resultsWereLimited[0] == "server":
                log.error(
                    "Too many matching resources in principal-property-search report"
                )
            responses.append(
                element.StatusResponse(
                    element.HRef.fromString(request.uri),
                    element.Status.fromResponseCode(
                        responsecode.INSUFFICIENT_STORAGE_SPACE),
                    element.Error(element.NumberOfMatchesWithinLimits()),
                    element.ResponseDescription("Results limited by %s at %d" %
                                                resultsWereLimited),
                ))
        returnValue(MultiStatusResponse(responses))
    def report_http___calendarserver_org_ns__calendarserver_principal_search(
            self, request, calendarserver_principal_search):
        """
        Generate a calendarserver-principal-search REPORT.

        @param request: Request object
        @param calendarserver_principal_search: CalendarServerPrincipalSearch object
        """

        # Verify root element
        if not isinstance(calendarserver_principal_search,
                          customxml.CalendarServerPrincipalSearch):
            msg = "%s expected as root element, not %s." % (
                customxml.CalendarServerPrincipalSearch.sname(),
                calendarserver_principal_search.sname())
            log.warn(msg)
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

        # Only handle Depth: 0
        depth = request.headers.getHeader("depth", "0")
        if depth != "0":
            log.error(
                "Error in calendarserver-principal-search REPORT, Depth set to %s"
                % (depth, ))
            raise HTTPError(
                StatusResponse(responsecode.BAD_REQUEST,
                               "Depth %s not allowed" % (depth, )))

        tokens, context, applyTo, clientLimit, propElement = extractCalendarServerPrincipalSearchData(
            calendarserver_principal_search)

        if not validateTokens(tokens):
            raise HTTPError(
                StatusResponse(responsecode.FORBIDDEN,
                               "Insufficient search token length"))

        # Run report
        resultsWereLimited = None
        resources = []
        if applyTo or not hasattr(self, "directory"):
            for principalCollection in self.principalCollections():
                uri = principalCollection.principalCollectionURL()
                resource = (yield request.locateResource(uri))
                if resource:
                    resources.append((resource, uri))
        else:
            resources.append((self, request.uri))

        # We need to access a directory service
        principalCollection = resources[0][0]
        dir = principalCollection.directory

        matchingResources = []
        matchcount = 0

        records = (yield dir.recordsMatchingTokens(tokens, context=context))

        for record in records:
            resource = yield principalCollection.principalForRecord(record)
            if resource:
                matchingResources.append(resource)

                # We've determined this is a matching resource
                matchcount += 1
                if clientLimit is not None and matchcount >= clientLimit:
                    resultsWereLimited = ("client", matchcount)
                    break
                if matchcount >= config.MaxPrincipalSearchReportResults:
                    resultsWereLimited = ("server", matchcount)
                    break

        # Generate the response
        responses = []
        for resource in matchingResources:
            url = resource.url()
            yield prop_common.responseForHref(
                request, responses, element.HRef.fromString(url), resource,
                prop_common.propertyListForResource, propElement)

        if resultsWereLimited is not None:
            if resultsWereLimited[0] == "server":
                log.error(
                    "Too many matching resources in calendarserver-principal-search report"
                )
            responses.append(
                element.StatusResponse(
                    element.HRef.fromString(request.uri),
                    element.Status.fromResponseCode(
                        responsecode.INSUFFICIENT_STORAGE_SPACE),
                    element.Error(element.NumberOfMatchesWithinLimits()),
                    element.ResponseDescription("Results limited by %s at %d" %
                                                resultsWereLimited),
                ))
        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!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_urn_ietf_params_xml_ns_carddav_addressbook_query(
        self, request, addressbook_query):
    """
    Generate an addressbook-query REPORT.
    (CardDAV, section 8.6)
    """
    # Verify root element
    if addressbook_query.qname() != (carddav_namespace, "addressbook-query"):
        raise ValueError(
            "{CardDAV:}addressbook-query expected as root element, not {elementName}."
            .format(elementName=addressbook_query.sname()))

    if not self.isCollection():
        parent = (yield self.locateParent(request, request.uri))
        if not parent.isAddressBookCollection():
            log.error(
                "addressbook-query report is not allowed on a resource outside of an address book collection {parent}",
                parent=self)
            raise HTTPError(
                StatusResponse(
                    responsecode.FORBIDDEN,
                    "Must be address book collection or address book resource")
            )

    responses = []

    xmlfilter = addressbook_query.filter
    filter = Filter(xmlfilter)
    query = addressbook_query.props
    limit = addressbook_query.limit

    assert query is not None

    if query.qname() == ("DAV:", "allprop"):
        propertiesForResource = report_common.allPropertiesForResource
        generate_address_data = False

    elif query.qname() == ("DAV:", "propname"):
        propertiesForResource = report_common.propertyNamesForResource
        generate_address_data = False

    elif query.qname() == ("DAV:", "prop"):
        propertiesForResource = report_common.propertyListForResource

        # Verify that any address-data element matches what we can handle
        result, message, generate_address_data = report_common.validPropertyListAddressDataTypeVersion(
            query)
        if not result:
            log.error(message)
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (carddav_namespace, "supported-address-data"),
                    "Invalid address-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" % (filter, ))
        raise HTTPError(
            ErrorResponse(
                responsecode.FORBIDDEN,
                (carddav_namespace, "valid-filter"),
                "Invalid filter element",
            ))

    matchcount = [
        0,
    ]
    max_number_of_results = [
        config.MaxQueryWithDataResults if generate_address_data else None,
    ]
    limited = [
        False,
    ]

    if limit:
        clientLimit = int(str(limit.childOfType(NResults)))
        if max_number_of_results[
                0] is None or clientLimit < max_number_of_results[0]:
            max_number_of_results[0] = clientLimit

    @inlineCallbacks
    def doQuery(addrresource, uri):
        """
        Run a query on the specified address book collection
        accumulating the query responses.
        @param addrresource: the L{CalDAVResource} for an address book collection.
        @param uri: the uri for the address book collecton resource.
        """
        def checkMaxResults():
            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])

        @inlineCallbacks
        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)

        @inlineCallbacks
        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())

        if not addrresource.isAddressBookCollection():

            #do UID lookup on last part of uri
            resource_name = urllib.unquote(uri[uri.rfind("/") + 1:])
            if resource_name.endswith(".vcf") and len(resource_name) > 4:

                # see if parent is directory backed address book
                parent = (yield addrresource.locateParent(request, uri))

        # Check whether supplied resource is an address book or an address book object resource
        if addrresource.isAddressBookCollection():

            if addrresource.isDirectoryBackedAddressBookCollection():
                yield maybeDeferred(queryDirectoryBackedAddressBook,
                                    addrresource, filter)

            else:

                # 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
                                addrresource.inheritedACEsforChildren(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 in (
                                yield addrresource.search(filter))
                        ]  #@UnusedVariable
                    except IndexedSearchException:
                        names = yield addrresource.listChildren()
                        index_query_ok = False
                    if not names:
                        return

                    # Now determine which valid resources are readable and which are not
                    ok_resources = []
                    yield addrresource.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_address_data or not index_query_ok:
                            vcard = yield child.vCard()
                            assert vcard is not None, "vCard {name} is missing from address book collection {collection!r}".format(
                                name=child_uri_name, collection=self)
                        else:
                            vcard = None

                        yield queryAddressBookObjectResource(
                            child,
                            uri,
                            child_uri_name,
                            vcard,
                            query_ok=index_query_ok)

        else:

            handled = False
            resource_name = urllib.unquote(uri[uri.rfind("/") + 1:])
            if resource_name.endswith(".vcf") and len(resource_name) > 4:

                # see if parent is directory backed address book
                parent = (yield addrresource.locateParent(request, uri))

                if parent.isDirectoryBackedAddressBookCollection():

                    vCardFilter = carddavxml.Filter(*[
                        carddavxml.PropertyFilter(
                            carddavxml.TextMatch.fromString(
                                resource_name[:-4]),
                            name="UID",  # attributes
                        ),
                    ])
                    vCardFilter = Filter(vCardFilter)

                    yield maybeDeferred(queryDirectoryBackedAddressBook,
                                        parent, vCardFilter)
                    handled = True

            if not handled:
                vcard = yield addrresource.vCard()
                yield queryAddressBookObjectResource(addrresource, uri, None,
                                                     vcard)

        if limited[0]:
            raise NumberOfMatchesWithinLimits(matchcount[0])

    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToAddressBookCollections(
            self, request, request.uri, depth, doQuery, (davxml.Read(), ))
    except NumberOfMatchesWithinLimits, e:
        self.log.info(
            "Too many matching components in addressbook-query report. Limited to {limit} items",
            limit=e.maxLimit())
        responses.append(
            davxml.StatusResponse(
                davxml.HRef.fromString(request.uri),
                davxml.Status.fromResponseCode(
                    responsecode.INSUFFICIENT_STORAGE_SPACE),
                davxml.Error(davxml.NumberOfMatchesWithinLimits()),
                davxml.ResponseDescription(
                    "Results limited to {limit} items".format(
                        limit=e.maxLimit())),
            ))
def report_DAV__expand_property(self, request, expand_property):
    """
    Generate an expand-property REPORT. (RFC 3253, section 3.8)

    TODO: for simplicity we will only support one level of expansion.
    """
    # Verify root element
    if not isinstance(expand_property, element.ExpandProperty):
        raise ValueError(
            "%s expected as root element, not %s." %
            (element.ExpandProperty.sname(), expand_property.sname()))

    # Only handle Depth: 0
    depth = request.headers.getHeader("depth", "0")
    if depth != "0":
        log.error("Non-zero depth is not allowed: %s" % (depth, ))
        raise HTTPError(
            StatusResponse(responsecode.BAD_REQUEST,
                           "Depth %s not allowed" % (depth, )))

    #
    # Get top level properties to expand and make sure we only have one level
    #
    properties = {}

    for property in expand_property.children:
        namespace = property.attributes.get("namespace", dav_namespace)
        name = property.attributes.get("name", "")

        # Make sure children have no children
        props_to_find = []
        for child in property.children:
            if child.children:
                log.error(
                    "expand-property REPORT only supports single level expansion"
                )
                raise HTTPError(
                    StatusResponse(
                        responsecode.NOT_IMPLEMENTED,
                        "expand-property REPORT only supports single level expansion"
                    ))
            child_namespace = child.attributes.get("namespace", dav_namespace)
            child_name = child.attributes.get("name", "")
            props_to_find.append((child_namespace, child_name))

        properties[(namespace, name)] = props_to_find

    #
    # Generate the expanded responses status for each top-level property
    #
    properties_by_status = {
        responsecode.OK: [],
        responsecode.NOT_FOUND: [],
    }

    filteredaces = None
    lastParent = None

    for qname in properties.iterkeys():
        try:
            prop = (yield self.readProperty(qname, request))

            # Form the PROPFIND-style DAV:prop element we need later
            props_to_return = element.PropertyContainer(*properties[qname])

            # Now dereference any HRefs
            responses = []
            for href in prop.children:
                if isinstance(href, element.HRef):

                    # Locate the Href resource and its parent
                    resource_uri = str(href)
                    child = (yield request.locateResource(resource_uri))

                    if not child or not child.exists():
                        responses.append(
                            element.StatusResponse(
                                href,
                                element.Status.fromResponseCode(
                                    responsecode.NOT_FOUND)))
                        continue
                    parent = (yield request.locateResource(
                        parentForURL(resource_uri)))

                    # Check privileges on parent - must have at least DAV:read
                    try:
                        yield parent.checkPrivileges(request,
                                                     (element.Read(), ))
                    except AccessDeniedError:
                        responses.append(
                            element.StatusResponse(
                                href,
                                element.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 privileges - must have at least DAV:read
                    try:
                        yield child.checkPrivileges(
                            request, (element.Read(), ),
                            inherited_aces=filteredaces)
                    except AccessDeniedError:
                        responses.append(
                            element.StatusResponse(
                                href,
                                element.Status.fromResponseCode(
                                    responsecode.FORBIDDEN)))
                        continue

                    # Now retrieve all the requested properties on the HRef resource
                    yield prop_common.responseForHref(
                        request,
                        responses,
                        href,
                        child,
                        prop_common.propertyListForResource,
                        props_to_return,
                    )

            prop.children = responses
            properties_by_status[responsecode.OK].append(prop)
        except:
            f = Failure()

            log.error(
                "Error reading property {qname} for resource {req}: {failure}",
                qname=qname,
                req=request.uri,
                failure=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))

    # Build the overall response
    propstats = [
        element.PropertyStatus(
            element.PropertyContainer(*properties_by_status[pstatus]),
            element.Status.fromResponseCode(pstatus))
        for pstatus in properties_by_status if properties_by_status[pstatus]
    ]

    returnValue(
        MultiStatusResponse(
            (element.PropertyStatusResponse(element.HRef(request.uri),
                                            *propstats), )))
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))
Beispiel #13
0
                    davxml.PropertyStatus(
                        davxml.PropertyContainer(),
                        davxml.Status.fromResponseCode(responsecode.OK)))

            xml_response = davxml.PropertyStatusResponse(
                davxml.HRef(uri), *propstats)

            # This needed for propfind cache tracking of children changes
            if depth == "1":
                if resource != self:
                    if hasattr(resource, "owner_url"):
                        request.childCacheURIs.append(resource.owner_url())
                    elif hasattr(resource, "url"):
                        request.childCacheURIs.append(resource.url())
        else:
            xml_response = davxml.StatusResponse(
                davxml.HRef(uri), davxml.Status.fromResponseCode(respcode))

        xml_responses.append(xml_response)

    if not hasattr(request, "extendedLogItems"):
        request.extendedLogItems = {}
    request.extendedLogItems["responses"] = len(xml_responses)

    #
    # Return response
    #
    returnValue(MultiStatusResponse(xml_responses))


##
# Utilities
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)))
Beispiel #16
0
    def _handleInvite(self, request, invitedoc):
        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 _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)

        setDict, removeDict, updateinviteDict = {}, {}, {}
        okusers = set()
        badusers = set()
        for item in invitedoc.children:
            if isinstance(item, customxml.InviteSet):
                userid, cn, access, summary = _handleInviteSet(item)
                setDict[userid] = (cn, access, summary)

                # Validate each userid on add only
                uid = (yield self.validUserIDForShare(userid, request))
                if uid is None:
                    uid = yield self.principalForCalendarGroupAddress(userid)
                (badusers if uid is None else okusers).add(userid)
            elif isinstance(item, customxml.InviteRemove):
                userid, access = _handleInviteRemove(item)
                removeDict[userid] = access

                # Treat removed userids as valid as we will fail invalid ones silently
                okusers.add(userid)

        # Only make changes if all OK
        if len(badusers) == 0:
            okusers = set()
            badusers = set()
            # Special case removing and adding the same user and treat that as an add
            sameUseridInRemoveAndSet = [
                u for u in removeDict.keys() if u in setDict
            ]
            for u in sameUseridInRemoveAndSet:
                removeACL = removeDict[u]
                cn, newACL, summary = setDict[u]
                updateinviteDict[u] = (cn, removeACL, newACL, summary)
                del removeDict[u]
                del setDict[u]
            for userid, access in removeDict.iteritems():
                result = (yield
                          self.uninviteUIDFromShare(userid, access, request))
                # If result is False that means the user being removed was not
                # actually invited, but let's not return an error in this case.
                okusers.add(userid)
            for userid, (cn, access, summary) in setDict.iteritems():
                result = (yield self.inviteUIDToShare(userid, cn, access,
                                                      summary, request))
                (okusers if result else badusers).add(userid)
            for userid, (cn, removeACL, newACL,
                         summary) in updateinviteDict.iteritems():
                result = (yield
                          self.inviteUserUpdateToShare(userid, cn, removeACL,
                                                       newACL, summary,
                                                       request))
                (okusers if result else badusers).add(userid)

            # In this case bad items do not prevent ok items from being processed
            ok_code = responsecode.OK
        else:
            # In this case a bad item causes all ok items not to be processed so failed dependency is returned
            ok_code = responsecode.FAILED_DEPENDENCY

        # Do a final validation of the entire set of invites
        invites = (yield self.validateInvites(request))
        numRecords = len(invites)

        # Set the sharing state on the collection
        shared = self.isShared()
        if shared and numRecords == 0:
            yield self.downgradeFromShare(request)
        elif not shared and numRecords != 0:
            yield self.upgradeToShare()

        # Create the multistatus response - only needed if some are bad
        if badusers:
            xml_responses = []
            xml_responses.extend([
                element.StatusResponse(
                    element.HRef(userid),
                    element.Status.fromResponseCode(ok_code))
                for userid in sorted(okusers)
            ])
            xml_responses.extend([
                element.StatusResponse(
                    element.HRef(userid),
                    element.Status.fromResponseCode(responsecode.FORBIDDEN))
                for userid in sorted(badusers)
            ])

            #
            # Return response
            #
            returnValue(MultiStatusResponse(xml_responses))
        else:
            returnValue(responsecode.OK)