Beispiel #1
0
        def get(self, qname, uid=None, cache=True):
            if cache:
                propertyCache = self.propertyCache()
                qnameuid = qname + (uid, )
                if qnameuid in propertyCache:
                    return propertyCache[qnameuid]
                else:
                    raise HTTPError(
                        StatusResponse(
                            responsecode.NOT_FOUND, "No such property: %s%s" %
                            (uid if uid else "", encodeXMLName(*qname))))

            self.log.debug("Read for %s%s on %s" %
                           (("{%s}:" % (uid, )) if uid else "", qname,
                            self.childPropertyStore.resource.fp.path))
            return self.childPropertyStore.get(qname, uid=uid)
Beispiel #2
0
    def listChildren(self):
        children = []
        if config.EnablePrincipalListings:
            try:
                for record in (yield self.directory.recordsWithRecordType(
                        self.recordType)):
                    for shortName in getattr(record, "shortNames", []):
                        children.append(shortName)
            except AttributeError:
                log.warn("Cannot list children of record type {rt}",
                         rt=self.recordType.name)
            returnValue(children)

        else:
            # Not a listable collection
            raise HTTPError(responsecode.FORBIDDEN)
Beispiel #3
0
    def generateFreeBusyResponse(self, recipient, responses, organizerProp, uid, event_details):

        # Extract the ATTENDEE property matching current recipient from the calendar data
        cuas = recipient.record.calendarUserAddresses
        attendeeProp = self.scheduler.calendar.getAttendeeProperty(cuas)

        try:
            fbresult = yield FreebusyQuery(
                organizer=self.scheduler.organizer,
                organizerProp=organizerProp,
                recipient=recipient,
                attendeeProp=attendeeProp,
                uid=uid,
                timerange=self.scheduler.timeRange,
                excludeUID=self.scheduler.excludeUID,
                logItems=self.scheduler.logItems,
                event_details=event_details,
            ).generateAttendeeFreeBusyResponse()
        except Exception as e:
            log.failure(
                "Could not determine free busy information for recipient {cuaddr}",
                cuaddr=recipient.cuaddr, level=LogLevel.debug
            )
            log.error(
                "Could not determine free busy information for recipient {cuaddr}: {ex}",
                cuaddr=recipient.cuaddr, ex=e
            )
            err = HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "recipient-permissions"),
                "Could not determine free busy information",
            ))
            responses.add(
                recipient.cuaddr,
                Failure(exc_value=err),
                reqstatus=iTIPRequestStatus.NO_AUTHORITY
            )
            returnValue(False)
        else:
            responses.add(
                recipient.cuaddr,
                responsecode.OK,
                reqstatus=iTIPRequestStatus.SUCCESS,
                calendar=fbresult
            )
            returnValue(True)
Beispiel #4
0
    def xmlRequestHandler(self, request):

        # Need to read the data and get the root element first
        xmldata = (yield allDataFromStream(request.stream))
        try:
            doc = element.WebDAVDocument.fromString(xmldata)
        except ValueError, e:
            self.log.error("Error parsing doc (%s) Doc:\n %s" % (
                str(e),
                xmldata,
            ))
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (customxml.calendarserver_namespace, "valid-request"),
                    "Invalid XML",
                ))
Beispiel #5
0
    def generateFreeBusyResponse(self, recipient, responses, organizerProp, organizerPrincipal, uid, event_details):

        # Extract the ATTENDEE property matching current recipient from the calendar data
        cuas = recipient.record.calendarUserAddresses
        attendeeProp = self.scheduler.calendar.getAttendeeProperty(cuas)

        remote = isinstance(self.scheduler.organizer, RemoteCalendarUser)

        try:
            fbresult = (yield self.generateAttendeeFreeBusyResponse(
                recipient,
                organizerProp,
                organizerPrincipal,
                uid,
                attendeeProp,
                remote,
                event_details,
            ))
        except Exception:
            log.failure(
                "Could not determine free busy information for recipient {cuaddr}",
                cuaddr=recipient.cuaddr, level=LogLevel.debug
            )
            log.error(
                "Could not determine free busy information for recipient {cuaddr}",
                cuaddr=recipient.cuaddr
            )
            err = HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "recipient-permissions"),
                "Could not determine free busy information",
            ))
            responses.add(
                recipient.cuaddr,
                Failure(exc_value=err),
                reqstatus=iTIPRequestStatus.NO_AUTHORITY
            )
            returnValue(False)
        else:
            responses.add(
                recipient.cuaddr,
                responsecode.OK,
                reqstatus=iTIPRequestStatus.SUCCESS,
                calendar=fbresult
            )
            returnValue(True)
Beispiel #6
0
    def loadOriginatorFromRequestDetails(self, request):
        # The originator is the owner of the Outbox. We will have checked prior to this
        # that the authenticated user has privileges to schedule as the owner.
        originator = ""
        originatorPrincipal = (yield self.ownerPrincipal(request))
        if originatorPrincipal:
            # Pick the canonical CUA:
            originator = originatorPrincipal.canonicalCalendarUserAddress()

        if not originator:
            self.log.error("{m} request must have Originator", m=self.method)
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "originator-specified"),
                "Missing originator",
            ))
        else:
            returnValue(originator)
Beispiel #7
0
    def loadRecipientsFromCalendarData(self, calendar):

        # Get the ATTENDEEs
        attendees = list()
        unique_set = set()
        for attendee, _ignore in calendar.getAttendeesByInstance():
            if attendee not in unique_set:
                attendees.append(attendee)
                unique_set.add(attendee)

        if not attendees:
            self.log.error("POST request must have at least one ATTENDEE")
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "recipient-specified"),
                "Must have recipients",
            ))
        else:
            return(list(attendees))
Beispiel #8
0
    def readProperty(self, property, request):
        if type(property) is tuple:
            qname = property
        else:
            qname = property.qname()

        namespace, name = qname

        if namespace == dav_namespace:
            if name == "resourcetype":
                rtype = self.resourceType()
                returnValue(rtype)

        elif namespace == calendarserver_namespace:
            if name == "expanded-group-member-set":
                principals = (yield self.expandedGroupMembers())
                returnValue(customxml.ExpandedGroupMemberSet(
                    *[element.HRef(p.principalURL()) for p in principals]
                ))

            elif name == "expanded-group-membership":
                principals = (yield self.expandedGroupMemberships())
                returnValue(customxml.ExpandedGroupMembership(
                    *[element.HRef(p.principalURL()) for p in principals]
                ))

            elif name == "record-type":
                if hasattr(self, "record"):
                    returnValue(
                        customxml.RecordType(
                            self.record.service.recordTypeToOldName(
                                self.record.recordType
                            )
                        )
                    )
                else:
                    raise HTTPError(StatusResponse(
                        responsecode.NOT_FOUND,
                        "Property %s does not exist." % (qname,)
                    ))

        result = (yield super(DAVPrincipalResource, self).readProperty(property, request))
        returnValue(result)
Beispiel #9
0
def extractCalendarServerPrincipalSearchData(doc):
    """
    Extract relevant info from a CalendarServerPrincipalSearch document

    @param doc: CalendarServerPrincipalSearch object to extract info from
    @return: A tuple containing:
        the list of tokens
        the context string
        the applyTo boolean
        the clientLimit integer
        the propElement containing the properties to return
    """
    context = doc.attributes.get("context", None)
    applyTo = False
    tokens = []
    clientLimit = None
    for child in doc.children:
        if child.qname() == (dav_namespace, "prop"):
            propElement = child

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

        elif child.qname() == (calendarserver_namespace, "search-token"):
            tokenValue = child.toString().strip()
            if tokenValue:
                tokens.append(tokenValue)

        elif child.qname() == (calendarserver_namespace, "limit"):
            try:
                nresults = child.childOfType(customxml.NResults)
                clientLimit = int(str(nresults))
            except (
                    TypeError,
                    ValueError,
            ):
                msg = "Bad XML: unknown value for <limit> element"
                log.warn(msg)
                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

    return tokens, context, applyTo, clientLimit, propElement
Beispiel #10
0
    def http_POST(self, request):
        """
        The server-to-server POST method.
        """

        # Need a transaction to work with
        txn = transactionFromRequest(request, self._newStore)

        # This is a server-to-server scheduling operation.
        scheduler = IScheduleScheduler(txn, None, podding=self._podding)

        # Check content first
        contentType = request.headers.getHeader("content-type")
        format = self.determineType(contentType)

        if format is None:
            msg = "MIME type {} not allowed in iSchedule request".format(contentType,)
            self.log.error(msg)
            raise HTTPError(scheduler.errorResponse(
                responsecode.FORBIDDEN,
                (ischedule_namespace, "invalid-calendar-data-type"),
                msg,
            ))

        originator = self.loadOriginatorFromRequestHeaders(request)
        recipients = self.loadRecipientsFromRequestHeaders(request)
        body = (yield allDataFromStream(request.stream))
        calendar = Component.fromString(body, format=format)

        # Do the POST processing treating this as a non-local schedule
        try:
            result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, calendar, originator, recipients))
        except Exception:
            ex = Failure()
            yield txn.abort()
            ex.raiseException()
        else:
            yield txn.commit()
        response = result.response(format=format)
        if not self._podding:
            response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber))
        returnValue(response)
Beispiel #11
0
 def _handleInviteRemove(inviteremove):
     userid = None
     access = []
     for item in inviteremove.children:
         if isinstance(item, element.HRef):
             userid = str(item)
             continue
         if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
             access.append(item)
             continue
     if userid is None:
         raise HTTPError(ErrorResponse(
             responsecode.FORBIDDEN,
             (customxml.calendarserver_namespace, "valid-request"),
             "Missing href: %s" % (inviteremove,),
         ))
     if len(access) == 0:
         access = None
     else:
         access = set(access)
     return (userid, access)
    def list(self, uid=None, filterByUID=True):
        """
        Enumerate the property names stored in extended attributes of the
        wrapped path.

        @param uid: The per-user identifier for per user properties.

        @return: A C{list} of property names as two-tuples of namespace URI and
            local name.
        """

        prefix = self.deadPropertyXattrPrefix
        try:
            attrs = iter(self.attrs)
        except IOError, e:
            if e.errno == errno.ENOENT:
                return []
            raise HTTPError(
                StatusResponse(statusForFailure(Failure()),
                               "Unable to list properties: %s",
                               (self.resource.fp.path, )))
Beispiel #13
0
def mkcollection(filepath):
    """
    Perform a X{MKCOL} on the given filepath.
    @param filepath: the L{FilePath} of the collection resource to create.
    @raise HTTPError: (containing an appropriate response) if the operation
        fails.
    @return: a deferred response with a status code of L{responsecode.CREATED}
        if the destination already exists, or L{responsecode.NO_CONTENT} if the
        destination was created by the X{MKCOL} operation.
    """
    try:
        os.mkdir(filepath.path)
        # Remove stat info from filepath because we modified it
        filepath.changed()
    except:
        raise HTTPError(
            statusForFailure(
                Failure(),
                "creating directory in MKCOL: %s" % (filepath.path, )))

    return succeed(responsecode.CREATED)
Beispiel #14
0
    def renderHTTP(self, request):
        """
        Do the reverse proxy request and return the response.

        @param request: the incoming request that needs to be proxied.
        @type request: L{Request}

        @return: Deferred L{Response}
        """

        self.log.info("{method} {uri} {proto}",
                      method=request.method,
                      uri=request.uri,
                      proto="HTTP/%s.%s" % request.clientproto)

        # Check for multi-hop
        if not self.allowMultiHop:
            x_server = request.headers.getHeader("x-forwarded-server")
            if x_server:
                for item in x_server:
                    if item.lower() == config.ServerHostName.lower():
                        raise HTTPError(
                            StatusResponse(responsecode.BAD_GATEWAY,
                                           "Too many x-forwarded-server hops"))

        clientPool = getHTTPClientPool(self.poolID)
        proxyRequest = ClientRequest(request.method, request.uri,
                                     request.headers, request.stream)

        # Need x-forwarded-(for|host|server) headers. First strip any existing ones out, then add ours
        proxyRequest.headers.removeHeader("x-forwarded-host")
        proxyRequest.headers.removeHeader("x-forwarded-for")
        proxyRequest.headers.removeHeader("x-forwarded-server")
        proxyRequest.headers.addRawHeader("x-forwarded-host", request.host)
        proxyRequest.headers.addRawHeader("x-forwarded-for",
                                          request.remoteAddr.host)
        proxyRequest.headers.addRawHeader("x-forwarded-server",
                                          config.ServerHostName)

        return clientPool.submitRequest(proxyRequest)
Beispiel #15
0
            def do(action, property, removing=False):
                """
                Perform action(property, request) while maintaining an
                undo queue.
                """
                has = waitForDeferred(self.hasProperty(property, request))
                yield has
                has = has.getResult()

                if has:
                    oldProperty = waitForDeferred(
                        self.readProperty(property, request))
                    yield oldProperty
                    oldProperty = oldProperty.getResult()

                    def undo():
                        return self.writeProperty(oldProperty, request)
                else:

                    def undo():
                        return self.removeProperty(property, request)

                try:
                    x = waitForDeferred(action(property, request))
                    yield x
                    x.getResult()
                except KeyError, e:
                    # Removing a non-existent property is OK according to WebDAV
                    if removing:
                        responses.add(responsecode.OK, property)
                        yield True
                        return
                    else:
                        # Convert KeyError exception into HTTPError
                        responses.add(
                            Failure(exc_value=HTTPError(
                                StatusResponse(responsecode.FORBIDDEN, str(
                                    e)))), property)
                        yield False
                        return
Beispiel #16
0
    def finalChecks(self):
        """
        Final checks before doing the actual scheduling.
        """

        # With implicit scheduling only certain types of iTIP operations are allowed for POST.

        if self.doingPOST:
            # Freebusy requests always processed
            if self.checkForFreeBusy():
                return

            # COUNTER and DECLINE-COUNTER allowed
            if self.calendar.propertyValue("METHOD") in ("COUNTER", "DECLINECOUNTER"):
                return

            # Anything else is not allowed. However, for compatibility we will optionally
            # return a success response for all attendees.
            if config.Scheduling.CalDAV.OldDraftCompatibility:
                self.fakeTheResult = True
            else:
                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid iTIP message for implicit scheduling"))
Beispiel #17
0
 def _handleInviteSet(inviteset):
     userid = None
     cn = None
     access = None
     summary = None
     for item in inviteset.children:
         if isinstance(item, element.HRef):
             userid = str(item)
             continue
         if isinstance(item, customxml.CommonName):
             cn = str(item)
             continue
         if isinstance(item, customxml.InviteSummary):
             summary = str(item)
             continue
         if isinstance(item, customxml.ReadAccess) or isinstance(
                 item, customxml.ReadWriteAccess):
             access = item
             continue
     if userid and access and summary:
         return (userid, cn, access, summary)
     else:
         error_text = []
         if userid is None:
             error_text.append("missing href")
         if access is None:
             error_text.append("missing access")
         if summary is None:
             error_text.append("missing summary")
         raise HTTPError(
             ErrorResponse(
                 responsecode.FORBIDDEN,
                 (customxml.calendarserver_namespace, "valid-request"),
                 "%s: %s" % (
                     ", ".join(error_text),
                     inviteset,
                 ),
             ))
    def delete(self, qname, uid=None):
        """
        Remove the extended attribute from the wrapped path which stores the
        property given by C{qname}.

        @param uid: The per-user identifier for per user properties.

        @param qname: The property to delete as a two-tuple of namespace URI
            and local name.
        """
        key = self._encode(qname, uid)
        try:
            try:
                self.attrs.remove(key)
            except KeyError:
                pass
            except IOError, e:
                if e.errno not in _ATTR_MISSING:
                    raise
        except:
            raise HTTPError(
                StatusResponse(statusForFailure(Failure()),
                               "Unable to delete property: %s", (key, )))
Beispiel #19
0
    def loadRecipientsFromRequestHeaders(self, request):
        # Get list of Recipient headers
        rawRecipients = request.headers.getRawHeaders("recipient")
        if rawRecipients is None or (len(rawRecipients) == 0):
            self.log.error(
                "{method} request must have at least one Recipient header",
                method=self.method,
            )
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (ischedule_namespace, "recipient-missing"),
                "No recipients",
            ))

        # Recipient header may be comma separated list
        recipients = []
        for rawRecipient in rawRecipients:
            for r in rawRecipient.split(","):
                r = r.strip()
                if len(r):
                    recipients.append(r)

        return recipients
    def readProperty(self, property, request):
        if type(property) is tuple:
            qname = property
        else:
            qname = property.qname()

        namespace, name = qname

        if namespace == calendarserver_namespace:
            if name == "record-type":
                if hasattr(self.parent, "record"):
                    returnValue(
                        customxml.RecordType(
                            self._recordTypeFromProxyType().description))
                else:
                    raise HTTPError(
                        StatusResponse(
                            responsecode.NOT_FOUND,
                            "Property %s does not exist." % (qname, )))

        result = (yield super(CalendarUserProxyPrincipalResource,
                              self).readProperty(property, request))
        returnValue(result)
Beispiel #21
0
def http_POST(self, request):

    # POST can only target an existing resource
    if not self.exists():
        log.error("Resource not found: {s!r}", s=self)
        raise HTTPError(responsecode.NOT_FOUND)

    # POST can support many different APIs

    # First look at query params
    if request.params:
        if request.params == "add-member":
            if config.EnableAddMember and hasattr(self,
                                                  "POST_handler_add_member"):
                request.submethod = "add-member"
                result = (yield self.POST_handler_add_member(request))
                returnValue(result)

    # Look for query arguments
    if request.args:
        action = request.args.get("action", ("", ))
        if len(action) == 1:
            action = action[0]
            if hasattr(self, "POST_handler_action"):
                request.submethod = action
                result = (yield self.POST_handler_action(request, action))
                returnValue(result)

    # Content-type handlers
    contentType = request.headers.getHeader("content-type")
    if contentType:
        if hasattr(self, "POST_handler_content_type"):
            result = (yield self.POST_handler_content_type(
                request, (contentType.mediaType, contentType.mediaSubtype)))
            returnValue(result)

    returnValue(responsecode.FORBIDDEN)
    def contains(self, qname, uid=None):
        """
        Determine whether the property given by C{qname} is stored in an
        extended attribute of the wrapped path.

        @param qname: The property to look up as a two-tuple of namespace URI
            and local name.

        @param uid: The per-user identifier for per user properties.

        @return: C{True} if the property exists, C{False} otherwise.
        """

        key = self._encode(qname, uid)
        try:
            self.attrs.get(key)
        except KeyError:
            return False
        except IOError, e:
            if e.errno in _ATTR_MISSING or e.errno == errno.ENOENT:
                return False
            raise HTTPError(
                StatusResponse(statusForFailure(Failure()),
                               "Unable to read property: %s" % (key, )))
Beispiel #23
0
def http_DELETE(self, request):
    """
    Respond to a DELETE request. (RFC 2518, section 8.6)
    """
    if not self.exists():
        log.error("File not found: %s" % (self, ))
        raise HTTPError(responsecode.NOT_FOUND)

    depth = request.headers.getHeader("depth", "infinity")

    #
    # Check authentication and access controls
    #
    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
    yield parent
    parent = parent.getResult()

    x = waitForDeferred(parent.authorize(request, (davxml.Unbind(), )))
    yield x
    x.getResult()

    x = waitForDeferred(deleteResource(request, self, request.uri, depth))
    yield x
    yield x.getResult()
Beispiel #24
0
    def checkSACL(self, request):
        """
        Check SACLs against the current request
        """

        topLevel = request.path.strip("/").split("/")[0]
        saclServices = self.saclMap.get(topLevel, None)
        if not saclServices:
            returnValue(True)

        try:
            authnUser, authzUser = yield self.authenticate(request)
        except Exception:
            response = (yield UnauthorizedResponse.makeResponse(
                request.credentialFactories,
                request.remoteAddr
            ))
            raise HTTPError(response)

        # SACLs are enabled in the plist, but there may not actually
        # be a SACL group assigned to this service.  Let's see if
        # unauthenticated users are allowed by calling CheckSACL
        # with an empty string.
        if authzUser is None:
            for saclService in saclServices:
                if checkSACL("", saclService):
                    # No group actually exists for this SACL, so allow
                    # unauthenticated access
                    returnValue(True)
            # There is a SACL group for at least one of the SACLs, so no
            # unauthenticated access
            response = (yield UnauthorizedResponse.makeResponse(
                request.credentialFactories,
                request.remoteAddr
            ))
            log.info("Unauthenticated user denied by SACLs")
            raise HTTPError(response)

        # Cache the authentication details
        request.authnUser = authnUser
        request.authzUser = authzUser

        # Figure out the "username" from the davxml.Principal object
        username = authzUser.record.shortNames[0]

        access = False
        for saclService in saclServices:
            if checkSACL(username, saclService):
                # Access is allowed
                access = True
                break

        # Mark SACLs as having been checked so we can avoid doing it
        # multiple times
        request.checkedSACL = True

        if access:
            returnValue(True)

        log.warn(
            "User {user!r} is not enabled with the {sacl!r} SACL(s)",
            user=username, sacl=saclServices
        )
        raise HTTPError(responsecode.FORBIDDEN)
Beispiel #25
0
    def locateChild(self, request, segments):

        for filter in self.contentFilters:
            request.addResponseFilter(filter[0], atEnd=filter[1])

        # Examine cookies for wiki auth token; if there, ask the paired wiki
        # server for the corresponding record name.  If that maps to a
        # principal, assign that to authnuser.

        # Also, certain non-browser clients send along the wiki auth token
        # sometimes, so we now also look for the presence of x-requested-with
        # header that the webclient sends.  However, in the case of a GET on
        # /webcal that header won't be sent so therefore we allow wiki auth
        # for any path in the authServiceMap even if that header is missing.
        allowWikiAuth = False
        topLevel = request.path.strip("/").split("/")[0]
        if self.authServiceMap.get(topLevel, False):
            allowWikiAuth = True

        if not hasattr(request, "checkedWiki"):
            # Only do this once per request
            request.checkedWiki = True

            wikiConfig = config.Authentication.Wiki
            cookies = request.headers.getHeader("cookie")
            requestedWith = request.headers.hasHeader("x-requested-with")
            if (
                wikiConfig["Enabled"] and
                (requestedWith or allowWikiAuth) and
                cookies is not None
            ):
                for cookie in cookies:
                    if cookie.name == wikiConfig["Cookie"]:
                        token = cookie.value
                        break
                else:
                    token = None

                if token is not None and token != "unauthenticated":
                    log.debug(
                        "Wiki sessionID cookie value: {token}", token=token
                    )

                    try:
                        uid = yield uidForAuthToken(token, wikiConfig["EndpointDescriptor"])
                        if uid == "unauthenticated":
                            uid = None

                    except WebError as w:
                        uid = None
                        # FORBIDDEN status means it's an unknown token
                        if int(w.status) == responsecode.NOT_FOUND:
                            log.debug(
                                "Unknown wiki token: {token}", token=token
                            )
                        else:
                            log.error(
                                "Failed to look up wiki token {token}: {msg}",
                                token=token,
                                msg=w.message
                            )

                    except Exception as e:
                        log.error(
                            "Failed to look up wiki token: {error}",
                            error=e
                        )
                        uid = None

                    if uid is not None:
                        log.debug(
                            "Wiki lookup returned uid: {uid}", uid=uid
                        )
                        principal = yield self.principalForUID(request, uid)

                        if principal:
                            log.debug(
                                "Wiki-authenticated principal {uid} "
                                "being assigned to authnUser and authzUser",
                                uid=uid
                            )
                            request.authzUser = request.authnUser = principal

        if not hasattr(request, "authzUser") and config.WebCalendarAuthPath:
            topLevel = request.path.strip("/").split("/")[0]
            if self.authServiceMap.get(topLevel, False):
                # We've not been authenticated and the auth service is enabled
                # for this resource, so redirect.

                # Use config.ServerHostName if no x-forwarded-host header,
                # otherwise use the final hostname in x-forwarded-host.
                host = request.headers.getRawHeaders(
                    "x-forwarded-host",
                    [config.ServerHostName]
                )[-1].split(",")[-1].strip()
                port = 443 if (config.EnableSSL or config.BehindTLSProxy) else 80
                scheme = "https" if config.EnableSSL else "http"

                response = RedirectResponse(
                    request.unparseURL(
                        host=host,
                        port=port,
                        scheme=scheme,
                        path=config.WebCalendarAuthPath,
                        querystring="redirect={}://{}{}".format(
                            scheme,
                            host,
                            request.path
                        )
                    ),
                    temporary=True
                )
                raise HTTPError(response)

        # We don't want the /inbox resource to pay attention to SACLs because
        # we just want it to use the hard-coded ACL for the imip reply user.
        # The /timezones resource is used by the wiki web calendar, so open
        # up that resource.
        if segments[0] in ("inbox", "timezones"):
            request.checkedSACL = True

        elif (
            (
                len(segments) > 2 and
                segments[0] in ("calendars", "principals") and
                (
                    segments[1] == "wikis" or
                    (
                        segments[1] == "__uids__" and
                        segments[2].startswith(WikiDirectoryService.uidPrefix)
                    )
                )
            )
        ):
            # This is a wiki-related calendar resource. SACLs are not checked.
            request.checkedSACL = True

            # The authzuser value is set to that of the wiki principal if
            # not already set.
            if not hasattr(request, "authzUser") and segments[2]:
                wikiUid = None
                if segments[1] == "wikis":
                    wikiUid = "{}{}".format(WikiDirectoryService.uidPrefix, segments[2])
                else:
                    wikiUid = segments[2]
                if wikiUid:
                    log.debug(
                        "Wiki principal {name} being assigned to authzUser",
                        name=wikiUid
                    )
                    request.authzUser = yield self.principalForUID(request, wikiUid)

        elif (
            self.useSacls and
            not hasattr(request, "checkedSACL")
        ):
            yield self.checkSACL(request)

        if config.RejectClients:
            #
            # Filter out unsupported clients
            #
            agent = request.headers.getHeader("user-agent")
            if agent is not None:
                for reject in config.RejectClients:
                    if reject.search(agent) is not None:
                        log.info("Rejecting user-agent: {agent}", agent=agent)
                        raise HTTPError(StatusResponse(
                            responsecode.FORBIDDEN,
                            "Your client software ({}) is not allowed to "
                            "access this service."
                            .format(agent)
                        ))

        if not hasattr(request, "authnUser"):
            try:
                authnUser, authzUser = yield self.authenticate(request)
                request.authnUser = authnUser
                request.authzUser = authzUser
            except (UnauthorizedLogin, LoginFailed):
                response = yield UnauthorizedResponse.makeResponse(
                    request.credentialFactories,
                    request.remoteAddr
                )
                raise HTTPError(response)

        if (
            config.EnableResponseCache and
            request.method == "PROPFIND" and
            not getattr(request, "notInCache", False) and
            len(segments) > 1
        ):

            try:
                if not getattr(request, "checkingCache", False):
                    request.checkingCache = True
                    response = yield self.responseCache.getResponseForRequest(
                        request
                    )
                    if response is None:
                        request.notInCache = True
                        raise KeyError("Not found in cache.")

                    returnValue((_CachedResponseResource(response), []))
            except KeyError:
                pass

        child = yield super(RootResource, self).locateChild(
            request, segments
        )
        returnValue(child)
def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
    """
    Generate a calendar-query REPORT.
    (CalDAV-access-09, section 7.6)
    """

    # Verify root element
    if calendar_query.qname() != (caldav_namespace, "calendar-query"):
        raise ValueError("{CalDAV:}calendar-query expected as root element, not %s." % (calendar_query.sname(),))

    if not self.isCollection():
        parent = (yield self.locateParent(request, request.uri))
        if not parent.isPseudoCalendarCollection():
            log.error("calendar-query report is not allowed on a resource outside of a calendar collection %s" % (self,))
            raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be calendar collection or calendar resource"))

    responses = []

    xmlfilter = calendar_query.filter
    filter = Filter(xmlfilter)
    props = calendar_query.props

    assert props is not None

    # Get the original timezone provided in the query, if any, and validate it now
    query_timezone = None
    query_tz = calendar_query.timezone
    if query_tz is not None and not query_tz.valid():
        msg = "CalDAV:timezone must contain one VTIMEZONE component only: %s" % (query_tz,)
        log.error(msg)
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            (caldav_namespace, "valid-calendar-data"),
            "Invalid calendar-data",
        ))
    if query_tz:
        filter.settimezone(query_tz)
        query_timezone = tuple(calendar_query.timezone.calendar().subcomponents())[0]

    if props.qname() == ("DAV:", "allprop"):
        propertiesForResource = report_common.allPropertiesForResource
        generate_calendar_data = False

    elif props.qname() == ("DAV:", "propname"):
        propertiesForResource = report_common.propertyNamesForResource
        generate_calendar_data = False

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

        # Verify that any calendar-data element matches what we can handle
        result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(props)
        if not result:
            log.error(message)
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "supported-calendar-data"),
                "Invalid calendar-data",
            ))

    else:
        raise AssertionError("We shouldn't be here")

    # Verify that the filter element is valid
    if (filter is None) or not filter.valid():
        log.error("Invalid filter element: %r" % (xmlfilter,))
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            (caldav_namespace, "valid-filter"),
            "Invalid filter element",
        ))

    matchcount = [0]
    max_number_of_results = [config.MaxQueryWithDataResults if generate_calendar_data else None, ]

    @inlineCallbacks
    def doQuery(calresource, uri):
        """
        Run a query on the specified calendar collection
        accumulating the query responses.
        @param calresource: the L{CalDAVResource} for a calendar collection.
        @param uri: the uri for the calendar collection resource.
        """

        @inlineCallbacks
        def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True):
            """
            Run a query on the specified calendar.
            @param resource: the L{CalDAVResource} for the calendar.
            @param uri: the uri of the resource.
            @param name: the name of the resource.
            @param calendar: the L{Component} calendar read from the resource.
            """

            # Handle private events access restrictions
            if not isowner:
                access = resource.accessMode
            else:
                access = None

            if query_ok or filter.match(calendar, access):
                # Check size of results is within limit
                matchcount[0] += 1
                if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]:
                    raise NumberOfMatchesWithinLimits(max_number_of_results[0])

                if name:
                    href = davxml.HRef.fromString(joinURL(uri, name))
                else:
                    href = davxml.HRef.fromString(uri)

                try:
                    yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone)
                except ConcurrentModification:
                    # This can happen because of a race-condition between the
                    # time we determine which resources exist and the deletion
                    # of one of these resources in another request.  In this
                    # case, we ignore the now missing resource rather
                    # than raise an error for the entire report.
                    log.error("Missing resource during query: %s" % (href,))

        # Check whether supplied resource is a calendar or a calendar object resource
        if calresource.isPseudoCalendarCollection():
            # Get the timezone property from the collection if one was not set in the query,
            # and store in the query filter for later use
            has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request))
            timezone = query_timezone
            if query_tz is None and has_prop:
                tz = (yield calresource.readProperty(CalendarTimeZone(), request))
                filter.settimezone(tz)
                timezone = tuple(tz.calendar().subcomponents())[0]

            # Do some optimization of access control calculation by determining any inherited ACLs outside of
            # the child resource loop and supply those to the checkPrivileges on each child.
            filteredaces = (yield calresource.inheritedACEsforChildren(request))

            # Check private events access status
            isowner = (yield calresource.isOwner(request))

            # Check for disabled access
            if filteredaces is not None:
                index_query_ok = True
                try:
                    # Get list of children that match the search and have read access
                    names = [name for name, ignore_uid, ignore_type in (yield calresource.search(filter))]
                except IndexedSearchException:
                    names = yield calresource.listChildren()
                    index_query_ok = False

                if not names:
                    returnValue(True)

                # Now determine which valid resources are readable and which are not
                ok_resources = []
                yield calresource.findChildrenFaster(
                    "1",
                    request,
                    lambda x, y: ok_resources.append((x, y)),
                    None,
                    None,
                    None,
                    names,
                    (davxml.Read(),),
                    inherited_aces=filteredaces
                )

                for child, child_uri in ok_resources:
                    child_uri_name = child_uri[child_uri.rfind("/") + 1:]

                    if generate_calendar_data or not index_query_ok:
                        calendar = (yield child.iCalendarForUser(request))
                        assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self)
                    else:
                        calendar = None

                    yield queryCalendarObjectResource(child, uri, child_uri_name, calendar, timezone, query_ok=index_query_ok, isowner=isowner)
        else:
            # Get the timezone property from the collection if one was not set in the query,
            # and store in the query object for later use
            timezone = query_timezone
            if query_tz is None:

                parent = (yield calresource.locateParent(request, uri))
                assert parent is not None and parent.isPseudoCalendarCollection()

                has_prop = (yield parent.hasProperty(CalendarTimeZone(), request))
                if has_prop:
                    tz = (yield parent.readProperty(CalendarTimeZone(), request))
                    filter.settimezone(tz)
                    timezone = tuple(tz.calendar().subcomponents())[0]

            # Check private events access status
            isowner = (yield calresource.isOwner(request))

            calendar = (yield calresource.iCalendarForUser(request))
            yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone)

        returnValue(True)

    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),))
    except TooManyInstancesError, ex:
        log.error("Too many instances need to be computed in calendar-query report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            MaxInstances.fromString(str(ex.max_allowed)),
            "Too many instances",
        ))
    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),))
    except TooManyInstancesError, ex:
        log.error("Too many instances need to be computed in calendar-query report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            MaxInstances.fromString(str(ex.max_allowed)),
            "Too many instances",
        ))
    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in calendar-query report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            davxml.NumberOfMatchesWithinLimits(),
            "Too many components",
        ))
    except TimeRangeLowerLimit, e:
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            caldavxml.MinDateTime(),
            "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),)
        ))
    except TimeRangeUpperLimit, e:
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            caldavxml.MaxDateTime(),
            "Time-range value too far in the future. Must be on or before %s." % (str(e.limit),)
        ))
Beispiel #28
0
 def listChildren(self):
     # Not a listable collection
     raise HTTPError(responsecode.FORBIDDEN)
Beispiel #29
0
 def createSimilarFile(self, path):
     log.error("Attempt to create clone %r of resource %r" % (path, self))
     raise HTTPError(responsecode.NOT_FOUND)
Beispiel #30
0
    #
    doc = waitForDeferred(davXMLFromStream(request.stream))
    yield doc
    try:
        doc = doc.getResult()
    except ValueError, e:
        log.error("Error while handling ACL body: %s" % (e,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))

    #
    # Set properties
    #
    if doc is None:
        error = "Request XML body is required."
        log.error("Error: {err}", err=error)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))

    #
    # Parse request
    #
    acl = doc.root_element
    if not isinstance(acl, davxml.ACL):
        error = ("Request XML body must be an acl element."
                 % (davxml.PropertyUpdate.sname(),))
        log.error("Error: {err}", err=error)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))

    #
    # Do ACL merger
    #
    result = waitForDeferred(self.mergeAccessControlList(acl, request))