Esempio n. 1
0
 async def jqueryMapResource(self, request: IRequest) -> KleinRenderable:
     """
     Endpoint for the jQuery map file.
     """
     request.setHeader(HeaderName.contentType.value, ContentType.json.value)
     return await self.cachedResource(request, self.jqueryMapSourceURL,
                                      f"{self.jqueryVersion}.min.map")
Esempio n. 2
0
def _extractURLparts(request: IRequest) -> Tuple[str, str, int, str, str]:
    """
    Extracts and decodes URI parts from C{request}.

    All strings must be UTF8-decodable.

    @param request: A Twisted Web request.

    @raise URLDecodeError: If one of the parts could not be decoded as UTF-8.

    @return: L{tuple} of the URL scheme, the server name, the server port, the
        path info and the script name.
    """
    server_name = request.getRequestHostname()
    if hasattr(request.getHost(), "port"):
        server_port = request.getHost().port
    else:
        server_port = 0
    if (bool(request.isSecure()), server_port) not in [
        (True, 443),
        (False, 80),
        (False, 0),
        (True, 0),
    ]:
        server_name = server_name + b":" + intToBytes(server_port)

    script_name = b""
    if request.prepath:
        script_name = b"/".join(request.prepath)

        if not script_name.startswith(b"/"):
            script_name = b"/" + script_name

    path_info = b""
    if request.postpath:
        path_info = b"/".join(request.postpath)

        if not path_info.startswith(b"/"):
            path_info = b"/" + path_info

    url_scheme = "https" if request.isSecure() else "http"

    utf8Failures = []
    try:
        server_name = server_name.decode("utf-8")
    except UnicodeDecodeError:
        utf8Failures.append(("SERVER_NAME", Failure()))
    try:
        path_text = path_info.decode("utf-8")
    except UnicodeDecodeError:
        utf8Failures.append(("PATH_INFO", Failure()))
    try:
        script_text = script_name.decode("utf-8")
    except UnicodeDecodeError:
        utf8Failures.append(("SCRIPT_NAME", Failure()))

    if utf8Failures:
        raise _URLDecodeError(utf8Failures)

    return url_scheme, server_name, server_port, path_text, script_text
Esempio n. 3
0
    def eventSourceResource(self, request: IRequest) -> KleinRenderable:
        """
        HTML5 EventSource endpoint.
        """
        self._log.debug("Event source connected: {id}", id=id(request))

        request.setHeader(
            HeaderName.contentType.value, ContentType.eventStream.value
        )

        self.storeObserver.addListener(request)

        def disconnected(f: Failure) -> None:
            f.trap(ConnectionDone)
            self._log.debug("Event source disconnected: {id}", id=id(request))
            self.storeObserver.removeListener(request)

        def finished(_: Any) -> None:
            # We don't expect anything to fire the returned deferred, so
            # this should never happen.
            self.storeObserver.removeListener(request)
            raise AssertionError("This was not expected")

        # Handle disconnect
        request.notifyFinish().addCallbacks(finished, disconnected)

        # Return an unfired deferred, so the connection doesn't close on this
        # end...
        return Deferred()
Esempio n. 4
0
    def eventSourceResource(self, request: IRequest) -> KleinRenderable:
        """
        HTML5 EventSource endpoint.
        """
        self._log.debug("Event source connected: {id}", id=id(request))

        request.setHeader(
            HeaderName.contentType.value, ContentType.eventStream.value
        )

        self.storeObserver.addListener(request)

        def disconnected(f: Failure) -> None:
            f.trap(ConnectionDone)
            self._log.debug("Event source disconnected: {id}", id=id(request))
            self.storeObserver.removeListener(request)

        def finished(_: Any) -> None:
            # We don't expect anything to fire the returned deferred, so
            # this should never happen.
            self.storeObserver.removeListener(request)
            raise AssertionError("This was not expected")

        # Handle disconnect
        request.notifyFinish().addCallbacks(finished, disconnected)

        # Return an unfired deferred, so the connection doesn't close on this
        # end...
        return Deferred()
Esempio n. 5
0
    def extractValue(self, request: IRequest) -> Any:
        """
        Extract a value from the request.

        In the case of key/value form posts, this attempts to reliably make the
        value into str.  In the case of a JSON post, however, it will simply
        extract the value from the top-level dictionary, which means it could
        be any arrangement of JSON-serializiable objects.
        """
        fieldName = self.formFieldName
        if fieldName is None:
            raise ValueError("Cannot extract unnamed form field.")
        contentType = request.getHeader(b"content-type")
        if contentType is not None and contentType.startswith(
                b"application/json"):
            # TODO: parse only once, please.
            parsed = request.getComponent(IParsedJSONBody)
            if parsed is None:
                request.content.seek(0)
                octets = request.content.read()
                characters = octets.decode("utf-8")
                parsed = json.loads(characters)
                request.setComponent(IParsedJSONBody, parsed)
            if fieldName not in parsed:
                return None
            return parsed[fieldName]
        allValues = request.args.get(fieldName.encode("utf-8"))
        if allValues:
            return allValues[0].decode("utf-8")
        else:
            return None
Esempio n. 6
0
 def render(self, request: IRequest) -> bytes:
     # File expires a long time from now.
     # RFC-2616 section 14.21: "HTTP/1.1 servers SHOULD NOT send Expires
     # dates more than one year in the future."
     request.setHeader('expires',
                       datetimeToString(getTime() + 365 * secondsPerDay))
     return super().render(request)
Esempio n. 7
0
 async def lscacheJSResource(self, request: IRequest) -> KleinRenderable:
     """
     Endpoint for lscache.
     """
     request.setHeader(HeaderName.contentType.value,
                       ContentType.javascript.value)
     return await self.cachedResource(request, self.lscacheJSSourceURL,
                                      f"{self.lscacheVersion}.min.js")
Esempio n. 8
0
def notFoundResponse(request: IRequest) -> KleinRenderable:
    """
    Respond with a NOT FOUND status.
    """
    log.debug("Resource not found: {request.uri}", request=request)

    request.setResponseCode(http.NOT_FOUND)
    return textResponse(request, "Not found")
Esempio n. 9
0
def notFoundResponse(request: IRequest) -> KleinRenderable:
    """
    Respond with a NOT FOUND status.
    """
    log.debug("Resource not found: {request.uri}", request=request)

    request.setResponseCode(http.NOT_FOUND)
    return textResponse(request, "Not found")
Esempio n. 10
0
def textResponse(request: IRequest, message: str) -> KleinRenderable:
    """
    Respond with the given text.
    """
    request.setHeader(HeaderName.contentType.value, ContentType.text.value)
    request.setHeader(HeaderName.etag.value,
                      str(hash(message)).encode("ascii"))
    return message.encode("utf-8")
Esempio n. 11
0
def noContentResponse(request: IRequest,
                      etag: Optional[str] = None) -> KleinRenderable:
    """
    Respond with no content.
    """
    request.setResponseCode(http.NO_CONTENT)
    if etag is not None:
        request.setHeader(HeaderName.etag.value, etag)
    return b""
Esempio n. 12
0
def textResponse(request: IRequest, message: str) -> KleinRenderable:
    """
    Respond with the given text.
    """
    request.setHeader(HeaderName.contentType.value, ContentType.text.value)
    request.setHeader(
        HeaderName.etag.value, str(hash(message)).encode("ascii")
    )
    return message.encode("utf-8")
Esempio n. 13
0
def methodNotAllowedResponse(request: IRequest) -> KleinRenderable:
    """
    Respond with a METHOD NOT ALLOWED status.
    """
    log.debug(
        "Method {request.method} not allowed for resource: {request.uri}",
        request=request)

    request.setResponseCode(http.NOT_ALLOWED)
    return textResponse(request, "HTTP method not allowed")
Esempio n. 14
0
    def valueError(self, request: IRequest, failure) -> KleinRenderable:
        """
        Error handler for :exc:`ValueError`.

        :param request: The request to respond to.

        :param failure: The failure that occurred.
        """
        request.setResponseCode(http.BAD_REQUEST)
        return "Invalid inputs provided."
Esempio n. 15
0
def forbiddenResponse(request: IRequest) -> KleinRenderable:
    """
    Respond with a FORBIDDEN status.
    """
    log.debug("Forbidden resource for user {user}: {request.uri}",
              request=request,
              user=getattr(request, "user", None))

    request.setResponseCode(http.FORBIDDEN)
    return textResponse(request, "Permission denied")
Esempio n. 16
0
 def render(self, request: IRequest) -> bytes:
     # Pick an encoding based on client and server preferences.
     # We strongly prefer gzip because we save on bandwidth and
     # have pre-compressed the resource.
     accept = AcceptedEncodings.parse(request.getHeader('accept-encoding'))
     if 4.0 * accept['gzip'] > accept['identity']:
         request.setHeader('Content-Encoding', 'gzip')
         return self.__gzippedResource.render(request)
     else:
         return super().render(request)
Esempio n. 17
0
def noContentResponse(
    request: IRequest, etag: Optional[str] = None
) -> KleinRenderable:
    """
    Respond with no content.
    """
    request.setResponseCode(http.NO_CONTENT)
    if etag is not None:
        request.setHeader(HeaderName.etag.value, etag)
    return b""
Esempio n. 18
0
def jsonBytes(request: IRequest,
              data: bytes,
              etag: Optional[str] = None) -> bytes:
    """
    Respond with encoded JSON text.
    """
    request.setHeader(HeaderName.contentType.value, ContentType.json.value)
    if etag is None:
        etag = sha1(data).hexdigest()
    request.setHeader(HeaderName.etag.value, etag)
    return data
Esempio n. 19
0
def forbiddenResponse(request: IRequest) -> KleinRenderable:
    """
    Respond with a FORBIDDEN status.
    """
    log.debug(
        "Forbidden resource for user {user}: {request.uri}",
        request=request, user=getattr(request, "user", None)
    )

    request.setResponseCode(http.FORBIDDEN)
    return textResponse(request, "Permission denied")
Esempio n. 20
0
def methodNotAllowedResponse(request: IRequest) -> KleinRenderable:
    """
    Respond with a METHOD NOT ALLOWED status.
    """
    log.debug(
        "Method {request.method} not allowed for resource: {request.uri}",
        request=request
    )

    request.setResponseCode(http.NOT_ALLOWED)
    return textResponse(request, "HTTP method not allowed")
Esempio n. 21
0
def renderElement(
    request: IRequest,
    element: IRenderable,
    doctype: Optional[bytes] = b"<!DOCTYPE html>",
    _failElement: Optional[Callable[[Failure], "Element"]] = None,
) -> object:
    """
    Render an element or other L{IRenderable}.

    @param request: The L{IRequest} being rendered to.
    @param element: An L{IRenderable} which will be rendered.
    @param doctype: A L{bytes} which will be written as the first line of
        the request, or L{None} to disable writing of a doctype.  The argument
        should not include a trailing newline and will default to the HTML5
        doctype C{'<!DOCTYPE html>'}.

    @returns: NOT_DONE_YET

    @since: 12.1
    """
    if doctype is not None:
        request.write(doctype)
        request.write(b"\n")

    if _failElement is None:
        _failElement = twisted.web.util.FailureElement

    d = flatten(request, element, request.write)

    def eb(failure: Failure) -> Optional[Deferred[None]]:
        _moduleLog.failure("An error occurred while rendering the response.",
                           failure=failure)
        site: Optional["twisted.web.server.Site"] = getattr(
            request, "site", None)
        if site is not None and site.displayTracebacks:
            assert _failElement is not None
            return flatten(request, _failElement(failure), request.write)
        else:
            request.write(
                b'<div style="font-size:800%;'
                b"background-color:#FFF;"
                b"color:#F00"
                b'">An error occurred while rendering the response.</div>')
            return None

    def finish(result: object, *, request: IRequest = request) -> object:
        request.finish()
        return result

    d.addErrback(eb)
    d.addBoth(finish)
    return NOT_DONE_YET
Esempio n. 22
0
            def wrapper(app: Any, request: IRequest, *args: Any,
                        **kwargs: Any) -> KleinRenderable:
                request.setHeader(
                    HeaderName.server.value,
                    f"Incident Management System/{version}",
                )

                # Capture authentication info if sent by the client, (ie. it's
                # been previously asked to authenticate), so we can log it, but
                # don't require authentication.
                app.config.authProvider.authenticateRequest(request,
                                                            optional=True)

                return f(app, request, *args, **kwargs)
Esempio n. 23
0
    def urlsEndpoint(self, request: IRequest) -> KleinRenderable:
        """
        JavaScript variables for service URLs.
        """
        urls = {
            k: getattr(URLs, k).asText()
            for k in URLs.__dict__ if not k.startswith("_")
        }

        request.setHeader(HeaderName.contentType.value,
                          ContentType.javascript.value)

        return "\n".join(("var url_{} = {};".format(k, jsonTextFromObject(v))
                          for k, v in urls.items()))
Esempio n. 24
0
    async def bootstrapResource(self, request: IRequest) -> KleinRenderable:
        """
        Endpoint for Bootstrap.
        """
        requestURL = URL.fromText(request.uri.decode("ascii"))

        # Remove URL prefix
        names = requestURL.path[len(URLs.bootstrapBase.path) - 1:]

        request.setHeader(HeaderName.contentType.value, ContentType.css.value)
        return await self.cachedZippedResource(request,
                                               self.bootstrapSourceURL,
                                               self.bootstrapVersion,
                                               self.bootstrapVersion, *names)
Esempio n. 25
0
def combinedLogFormatter(timestamp: str, request: IRequest) -> str:
    """
    @return: A combined log formatted log line for the given request.

    @see: L{IAccessLogFormatter}
    """
    referrer = _escape(request.getHeader("referer") or "-")
    agent = _escape(request.getHeader("user-agent") or "-")

    clientIP = request.getClientIP()

    forwardedFor = (
        request.requestHeaders
        .getRawHeaders(b"x-forwarded-for", [b""])[0]
        .split(b",")[0]
        .strip()
    ).decode("charmap")

    if forwardedFor:
        ip = f"{forwardedFor} > {clientIP}"
    else:
        ip = clientIP


    if hasattr(request, "user") and request.user is not None:
        username = request.user.shortNames[0]
        try:
            username = _escape(username)
        except Exception:
            username = _escape(repr(username))
    else:
        username = "******"

    line = (
        '"{ip}" {user} - {timestamp} "{method} {uri} {protocol}" '
        '{code} {length} "{referrer}" "{agent}"'.format(
            ip=_escape(ip or "-"),
            timestamp=timestamp,
            method=_escape(request.method),
            uri=_escape(request.uri),
            protocol=_escape(request.clientproto),
            code=request.code,
            length=request.sentLength or "-",
            referrer=referrer,
            agent=agent,
            user=username,
        )
    )
    return line
Esempio n. 26
0
def internalErrorResponse(request: IRequest,
                          message: Optional[str] = None) -> KleinRenderable:
    """
    Respond with an INTERNAL SERVER ERROR status.
    """
    log.critical("Internal error for resource: {request.uri}: {message}",
                 request=request,
                 message=message)

    request.setResponseCode(http.INTERNAL_SERVER_ERROR)
    if message is None:
        message = "Internal error"
    else:
        message = f"{message}"
    return textResponse(request, message)
Esempio n. 27
0
def badRequestResponse(request: IRequest,
                       message: Optional[str] = None) -> KleinRenderable:
    """
    Respond with a BAD REQUEST status.
    """
    log.debug("Bad request for resource: {request.uri}: {message}",
              request=request,
              message=message)

    request.setResponseCode(http.BAD_REQUEST)
    if message is None:
        message = "Bad request"
    else:
        message = str(message)
    return textResponse(request, message)
Esempio n. 28
0
    async def authorizeRequest(
        self,
        request: IRequest,
        event: Optional[Event],
        requiredAuthorizations: Authorization,
    ) -> None:
        """
        Determine whether the user attached to a request has the required
        authorizations in the context of a given event.
        """
        self.authenticateRequest(request)

        userAuthorizations = await self.authorizationsForUser(
            request.user, event
        )
        request.authorizations = userAuthorizations

        if not (requiredAuthorizations & userAuthorizations):
            self._log.debug(
                "Authorization failed for {request.user}. "
                "Requires {requiredAuthorizations}, has {userAuthorizations}. "
                "URI: {request.uri}",
                request=request,
                requiredAuthorizations=requiredAuthorizations,
                userAuthorizations=userAuthorizations,
            )
            raise NotAuthorizedError("User not authorized")
Esempio n. 29
0
    def _playback(self, listener: IRequest,
                  lastEventID: Optional[str]) -> None:
        if lastEventID is None:
            return

        observerID, counterString = lastEventID.split(":")

        if observerID == str(id(self)):
            counter = int(counterString)
        else:
            # lastEventID came from a different observer
            counter = 0

        for eventCounter, event in self._events:
            if eventCounter >= counter:
                listener.write(event.render().encode("utf-8"))
    async def authorizeRequest(
        self, request: IRequest, event: Optional[Event],
        requiredAuthorizations: Authorization,
    ) -> None:
        """
        Determine whether the user attached to a request has the required
        authorizations in the context of a given event.
        """
        self.authenticateRequest(request)

        userAuthorizations = await self.authorizationsForUser(
            request.user, event
        )
        request.authorizations = userAuthorizations

        if not (requiredAuthorizations & userAuthorizations):
            self._log.debug(
                "Authorization failed for {request.user}. "
                "Requires {requiredAuthorizations}, has {userAuthorizations}. "
                "URI: {request.uri}",
                request=request,
                requiredAuthorizations=requiredAuthorizations,
                userAuthorizations=userAuthorizations,
            )
            raise NotAuthorizedError(f"User not authorized")
Esempio n. 31
0
 def urlFor(
     self,
     request: IRequest,
     endpoint: str,
     values: Optional[Mapping[str, KleinQueryValue]] = None,
     method: Optional[str] = None,
     force_external: bool = False,
     append_unknown: bool = True,
 ) -> str:
     host = request.getHeader(b"host")
     if host is None:
         if force_external:
             raise ValueError(
                 "Cannot build external URL if request"
                 " doesn't contain Host header"
             )
         host = b""
     return buildURL(
         self.url_map.bind(host),
         endpoint,
         values,
         method,
         force_external,
         append_unknown,
     )
Esempio n. 32
0
            def wrapper(
                app: Any, request: IRequest, *args: Any, **kwargs: Any
            ) -> KleinRenderable:
                request.setHeader(
                    HeaderName.server.value,
                    f"Incident Management System/{version}",
                )

                # Capture authentication info if sent by the client, (ie. it's
                # been previously asked to authenticate), so we can log it, but
                # don't require authentication.
                app.config.authProvider.authenticateRequest(
                    request, optional=True
                )

                return f(app, request, *args, **kwargs)
Esempio n. 33
0
def combinedLogFormatter(timestamp: str, request: IRequest) -> str:
    """
    @return: A combined log formatted log line for the given request.

    @see: L{IAccessLogFormatter}
    """
    referrer = _escape(request.getHeader("referer") or "-")
    agent = _escape(request.getHeader("user-agent") or "-")

    clientIP = request.getClientIP()

    forwardedFor = (
        request.requestHeaders.getRawHeaders(b"x-forwarded-for", [b""])[0]
        .split(b",")[0]
        .strip()
    ).decode("charmap")

    if forwardedFor:
        ip = f"{forwardedFor} > {clientIP}"
    else:
        ip = clientIP

    if hasattr(request, "user") and request.user is not None:
        username = request.user.shortNames[0]
        try:
            username = _escape(username)
        except Exception:
            username = _escape(repr(username))
    else:
        username = "******"

    line = (
        '"{ip}" {user} - {timestamp} "{method} {uri} {protocol}" '
        '{code} {length} "{referrer}" "{agent}"'.format(
            ip=_escape(ip or "-"),
            timestamp=timestamp,
            method=_escape(request.method),
            uri=_escape(request.uri),
            protocol=_escape(request.clientproto),
            code=request.code,
            length=request.sentLength or "-",
            referrer=referrer,
            agent=agent,
            user=username,
        )
    )
    return line
    def _playback(
        self, listener: IRequest, lastEventID: Optional[str]
    ) -> None:
        if lastEventID is None:
            return

        observerID, counterString = lastEventID.split(":")

        if observerID == str(id(self)):
            counter = int(counterString)
        else:
            # lastEventID came from a different observer
            counter = 0

        for eventCounter, event in self._events:
            if eventCounter >= counter:
                listener.write(event.render().encode("utf-8"))
    def authenticateRequest(
        self, request: IRequest, optional: bool = False
    ) -> None:
        """
        Authenticate a request.

        @param request: The request to authenticate.

        @param optional: If true, do not raise NotAuthenticatedError() if no
            user is associated with the request.
        """
        session = request.getSession()
        request.user = getattr(session, "user", None)

        if request.user is None and not optional:
            self._log.debug("Authentication failed")
            raise NotAuthenticatedError("No user logged in")
Esempio n. 36
0
def badRequestResponse(
    request: IRequest, message: Optional[str] = None
) -> KleinRenderable:
    """
    Respond with a BAD REQUEST status.
    """
    log.debug(
        "Bad request for resource: {request.uri}: {message}",
        request=request, message=message
    )

    request.setResponseCode(http.BAD_REQUEST)
    if message is None:
        message = "Bad request"
    else:
        message = str(message)
    return textResponse(request, message)
Esempio n. 37
0
def internalErrorResponse(
    request: IRequest, message: Optional[str] = None
) -> KleinRenderable:
    """
    Respond with an INTERNAL SERVER ERROR status.
    """
    log.critical(
        "Internal error for resource: {request.uri}: {message}",
        request=request, message=message
    )

    request.setResponseCode(http.INTERNAL_SERVER_ERROR)
    if message is None:
        message = "Internal error"
    else:
        message = f"{message}"
    return textResponse(request, message)
Esempio n. 38
0
def get_request_uri(request: IRequest) -> bytes:
    """Return the full URI that was requested by the client"""
    return b"%s://%s%s" % (
        b"https" if request.isSecure() else b"http",
        _get_requested_host(request),
        # despite its name, "request.uri" is only the path and query-string.
        request.uri,
    )
Esempio n. 39
0
 def mymethod(instance: Any, request: IRequest, *args: Any,
              **kw: Any) -> Any:
     data = yield _call(instance, method, request, *args, **kw)
     if _should_return_json(request):
         json_data = self._defaults.copy()
         json_data.update(data)
         for ignored in self._presentationSlots:
             json_data.pop(ignored, None)
         request.setHeader(b"content-type", b"application/json")
         ready = yield resolveDeferredObjects(json_data)
         result = dumps(ready)
     else:
         data[self.CONTENT] = loader.load()
         request.setHeader(b"content-type",
                           b"text/html; charset=utf-8")
         result = self._elementify(instance, data)
     returnValue(result)
Esempio n. 40
0
    def authenticateRequest(
        self, request: IRequest, optional: bool = False
    ) -> None:
        """
        Authenticate a request.

        @param request: The request to authenticate.

        @param optional: If true, do not raise NotAuthenticatedError() if no
            user is associated with the request.
        """
        session = request.getSession()
        request.user = getattr(session, "user", None)

        if request.user is None and not optional:
            self._log.debug("Authentication failed")
            raise NotAuthenticatedError("No user logged in")
Esempio n. 41
0
    def urlsEndpoint(self, request: IRequest) -> KleinRenderable:
        """
        JavaScript variables for service URLs.
        """
        urls = {
            k: getattr(URLs, k).asText() for k in URLs.__dict__
            if not k.startswith("_")
        }

        request.setHeader(
            HeaderName.contentType.value, ContentType.javascript.value
        )

        return "\n".join((
            "var url_{} = {};".format(k, jsonTextFromObject(v))
            for k, v in urls.items()
        ))
Esempio n. 42
0
    def logout(self, request: IRequest) -> KleinRenderable:
        """
        Endpoint for logging out.
        """
        session = request.getSession()
        session.expire()

        # Redirect back to application home
        return redirect(request, URLs.app)
Esempio n. 43
0
def get_username_mapping_session_cookie_from_request(request: IRequest) -> str:
    """Extract the session ID from the cookie

    Raises a SynapseError if the cookie isn't found
    """
    session_id = request.getCookie(USERNAME_MAPPING_SESSION_COOKIE_NAME)
    if not session_id:
        raise SynapseError(code=400, msg="missing session_id")
    return session_id.decode("ascii", errors="replace")
Esempio n. 44
0
    def logout(self, request: IRequest) -> KleinRenderable:
        """
        Endpoint for logging out.
        """
        session = request.getSession()
        session.expire()

        # Redirect back to application home
        return redirect(request, URLs.app)
    async def authorizeRequestForIncidentReport(
        self, request: IRequest, incidentReport: IncidentReport
    ) -> None:
        """
        Determine whether the user attached to a request has the required
        authorizations to read the incident report with the given number.
        """
        # The author of the incident report should be allowed to read and write
        # to it.

        if request.user is not None and incidentReport.reportEntries:
            rangerHandle = request.user.rangerHandle
            for reportEntry in incidentReport.reportEntries:
                if reportEntry.author == rangerHandle:
                    request.authorizations = (
                        Authorization.readIncidentReports |
                        Authorization.writeIncidentReports
                    )
                    return

        # If there are incidents attached to this incident report, then the
        # permissions on the attached incidents (which are determined by the
        # events containing the incidents) determine the permission on the
        # incident report.
        # So we'll iterate over all of the events containing incidents that
        # this incident report is attached to, and see if any of those events
        # can approve the request.

        events = frozenset(
            event for event, _incidentNumber in
            await self.store.incidentsAttachedToIncidentReport(
                incidentReport.number
            )
        )

        if events:
            for event in events:
                # There are incidents attached; use the authorization for
                # reading incidents from the corresponding events.
                # Because it's possible for multiple incidents to be attached,
                # if one event fails, keep trying the others in case they allow
                # it.
                try:
                    await self.authorizeRequest(
                        request, event, Authorization.readIncidents
                    )
                except NotAuthorizedError as e:
                    authFailure = e
                else:
                    return

            raise authFailure

        # Incident report is detached
        await self.authorizeRequest(
            request, None, Authorization.readIncidentReports
        )
Esempio n. 46
0
    def assertTextResponse(self, request: IRequest) -> str:
        """
        Assert that the response is text and return the text.
        """
        self.assertResponseCode(request, http.OK)
        self.assertResponseContentType(request, ContentType.text.value)

        # FIXME: Check encoding, default to UTF-8

        return cast(bytes, request.getWrittenData()).decode()
Esempio n. 47
0
        def notAuthenticatedError(
            app: Any, request: IRequest, failure: Failure
        ) -> KleinRenderable:
            """
            Not authenticated.
            """
            requestedWith = request.getHeader("X-Requested-With")
            if requestedWith is not None:
                if requestedWith == "XMLHttpRequest":
                    return forbiddenResponse(request)

            element = redirect(request, URLs.login, origin="o")
            return renderElement(request, element)
Esempio n. 48
0
    def if_authz_failed(self, request: IRequest, tag: Tag) -> KleinRenderable:
        """
        Render conditionally if the user failed to authorize.
        """
        if self.failed:
            # authn failed, not authz
            return ""

        session = request.getSession()
        user = getattr(session, "user", None)

        if user is None:
            return ""

        # We have a user but still got sent to login page
        return tag
Esempio n. 49
0
    async def loginSubmit(self, request: IRequest) -> KleinRenderable:
        """
        Endpoint for a login form submission.
        """
        username = queryValue(request, "username")
        password = queryValue(request, "password", default="")

        if username is None:
            user = None
        else:
            user = await self.config.authProvider.lookupUserName(username)

        if user is None:
            self._log.debug(
                "Login failed: no such user: {username}", username=username
            )
        else:
            if password is None:
                return invalidQueryResponse(request, "password")

            authenticated = await self.config.authProvider.verifyCredentials(
                user, password
            )

            if authenticated:
                session = request.getSession()
                session.user = user

                url = queryValue(request, "o")
                if url is None:
                    location = URLs.app  # Default to application home
                else:
                    location = URL.fromText(url)

                return redirect(request, location)
            else:
                self._log.debug(
                    "Login failed: incorrect credentials for user: {user}",
                    user=user
                )

        return self.login(request, failed=True)
Esempio n. 50
0
def redirect(
    request: IRequest, location: URL, origin: Optional[str] = None
) -> KleinRenderable:
    """
    Perform a redirect.
    """
    if origin is not None:
        try:
            location = location.set(origin, request.uri.decode("utf-8"))
        except ValueError:
            return badRequestResponse(request, "Invalid origin URI")

    log.debug(
        "Redirect {source} -> {destination}",
        source=request.uri.decode("utf-8"), destination=location.asText(),
    )
    url = location.asText().encode("utf-8")

    request.setHeader(HeaderName.contentType.value, ContentType.html.value)
    request.setHeader(HeaderName.location.value, url)
    request.setResponseCode(http.FOUND)

    return RedirectPage(location=location)
Esempio n. 51
0
    async def newIncidentResource(
        self, request: IRequest, eventID: str
    ) -> KleinRenderable:
        """
        New incident endpoint.
        """
        event = Event(id=eventID)

        await self.config.authProvider.authorizeRequest(
            request, event, Authorization.writeIncidents
        )

        try:
            json = objectFromJSONBytesIO(request.content)
        except JSONDecodeError as e:
            return invalidJSONResponse(request, e)

        author = request.user.shortNames[0]
        now = DateTime.now(TimeZone.utc)
        jsonNow = jsonObjectFromModelObject(now)

        # Set JSON incident number to 0
        # Set JSON incident created time to now

        for incidentKey in (
            IncidentJSONKey.number,
            IncidentJSONKey.created,
        ):
            if incidentKey.value in json:
                return badRequestResponse(
                    request,
                    f"New incident may not specify {incidentKey.value}"
                )

        json[IncidentJSONKey.number.value] = 0
        json[IncidentJSONKey.created.value] = jsonNow

        # If not provided, set JSON event, state to new, priority to normal

        if IncidentJSONKey.event.value not in json:
            json[IncidentJSONKey.event.value] = event.id

        if IncidentJSONKey.state.value not in json:
            json[IncidentJSONKey.state.value] = (
                IncidentStateJSONValue.new.value
            )

        if IncidentJSONKey.priority.value not in json:
            json[IncidentJSONKey.priority.value] = (
                IncidentPriorityJSONValue.normal.value
            )

        # If not provided, set JSON handles, types, entries,
        # incident report numbers to an empty list

        for incidentKey in (
            IncidentJSONKey.rangerHandles,
            IncidentJSONKey.incidentTypes,
            IncidentJSONKey.reportEntries,
            IncidentJSONKey.incidentReportNumbers,
        ):
            if incidentKey.value not in json:
                json[incidentKey.value] = []

        # Set JSON report entry created time to now
        # Set JSON report entry author
        # Set JSON report entry automatic=False

        for entryJSON in json[IncidentJSONKey.reportEntries.value]:
            for reportEntryKey in (
                ReportEntryJSONKey.created,
                ReportEntryJSONKey.author,
                ReportEntryJSONKey.automatic,
            ):
                if reportEntryKey.value in entryJSON:
                    return badRequestResponse(
                        request,
                        f"New report entry may not specify "
                        f"{reportEntryKey.value}"
                    )

            entryJSON[ReportEntryJSONKey.created.value] = jsonNow
            entryJSON[ReportEntryJSONKey.author.value] = author
            entryJSON[ReportEntryJSONKey.automatic.value] = False

        # Deserialize JSON incident

        try:
            incident = modelObjectFromJSONObject(json, Incident)
        except JSONCodecError as e:
            return badRequestResponse(request, str(e))

        # Validate data

        if incident.event != event:
            return badRequestResponse(
                request,
                f"Incident's event {incident.event} does not match event in "
                f"URL {event}"
            )

        # Store the incident

        incident = await self.config.store.createIncident(incident, author)

        self._log.info(
            "User {author} created new incident #{incident.number} via JSON",
            author=author, incident=incident
        )
        self._log.debug(
            "New incident: {json}", json=jsonObjectFromModelObject(incident)
        )

        request.setHeader("Incident-Number", incident.number)
        request.setHeader(
            HeaderName.location.value,
            f"{URLs.incidentNumber.asText()}/{incident.number}"
        )
        return noContentResponse(request)
Esempio n. 52
0
    async def newIncidentReportResource(
        self, request: IRequest
    ) -> KleinRenderable:
        """
        New incident report endpoint.
        """
        await self.config.authProvider.authorizeRequest(
            request, None, Authorization.writeIncidentReports
        )

        try:
            json = objectFromJSONBytesIO(request.content)
        except JSONDecodeError as e:
            return invalidJSONResponse(request, e)

        author = request.user.shortNames[0]
        now = DateTime.now(TimeZone.utc)
        jsonNow = jsonObjectFromModelObject(now)

        # Set JSON incident report number to 0
        # Set JSON incident report created time to now

        for incidentReportKey in (
            IncidentReportJSONKey.number,
            IncidentReportJSONKey.created,
        ):
            if incidentReportKey.value in json:
                return badRequestResponse(
                    request,
                    f"New incident report may not specify "
                    f"{incidentReportKey.value}"
                )

        json[IncidentReportJSONKey.number.value] = 0
        json[IncidentReportJSONKey.created.value] = jsonNow

        # If not provided, set JSON report entries to an empty list

        if IncidentReportJSONKey.reportEntries.value not in json:
            json[IncidentReportJSONKey.reportEntries.value] = []

        # Set JSON report entry created time to now
        # Set JSON report entry author
        # Set JSON report entry automatic=False

        for entryJSON in json[IncidentReportJSONKey.reportEntries.value]:
            for reportEntryKey in (
                ReportEntryJSONKey.created,
                ReportEntryJSONKey.author,
                ReportEntryJSONKey.automatic,
            ):
                if reportEntryKey.value in entryJSON:
                    return badRequestResponse(
                        request,
                        f"New report entry may not specify "
                        f"{reportEntryKey.value}"
                    )

            entryJSON[ReportEntryJSONKey.created.value] = jsonNow
            entryJSON[ReportEntryJSONKey.author.value] = author
            entryJSON[ReportEntryJSONKey.automatic.value] = False

        # Deserialize JSON incident report

        try:
            incidentReport = modelObjectFromJSONObject(json, IncidentReport)
        except JSONCodecError as e:
            return badRequestResponse(request, str(e))

        # Store the incident report

        incidentReport = await self.config.store.createIncidentReport(
            incidentReport, author
        )

        self._log.info(
            "User {author} created new incident report "
            "#{incidentReport.number} via JSON",
            author=author, incidentReport=incidentReport
        )
        self._log.debug(
            "New incident report: {json}",
            json=jsonObjectFromModelObject(incidentReport),
        )

        request.setHeader("Incident-Report-Number", incidentReport.number)
        request.setHeader(
            HeaderName.location.value,
            f"{URLs.incidentNumber.asText()}/{incidentReport.number}"
        )
        return noContentResponse(request)