Ejemplo n.º 1
0
 def error(self):
     """
     Convert any 2xx codes in the propstat responses to 424 Failed
     Dependency.
     """
     for index, propstat in enumerate(self.propstats):
         # Check the status
         changed_status = False
         newchildren = []
         for child in propstat.children:
             if isinstance(child, element.Status) and (child.code / 100
                                                       == 2):
                 # Change the code
                 newchildren.append(
                     element.Status.fromResponseCode(
                         responsecode.FAILED_DEPENDENCY))
                 changed_status = True
             elif changed_status and isinstance(
                     child, element.ResponseDescription):
                 newchildren.append(
                     element.ResponseDescription(responsecode.RESPONSES[
                         responsecode.FAILED_DEPENDENCY]))
             else:
                 newchildren.append(child)
         self.propstats[index] = element.PropertyStatus(*newchildren)
Ejemplo n.º 2
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))
Ejemplo n.º 3
0
    def add(self, what, property):
        """
        Add a response.
        @param what: a status code or a L{Failure} for the given path.
        @param property: the property whose status is being reported.
        """
        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 len(property.children) > 0:
            # Re-instantiate as empty element.
            property = element.WebDAVUnknownElement.withName(
                property.namespace, property.name)

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

        children = []
        children.append(element.PropertyContainer(property))
        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.propstats.append(element.PropertyStatus(*children))
Ejemplo n.º 4
0
def propertyValue(value):
    return davxml.ResponseDescription(value)
Ejemplo n.º 5
0
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))
Ejemplo n.º 6
0
    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_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())),
            ))