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)
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")
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()
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")
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")
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""
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")
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)
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""
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
def writeJSONStream( request: IRequest, jsonStream: Iterable[bytes], etag: Optional[str] = None, ) -> None: """ Respond with a stream of JSON data. """ request.setHeader(HeaderName.contentType.value, ContentType.json.value) if etag is not None: request.setHeader(HeaderName.etag.value, etag) for line in jsonStream: request.write(line)
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)
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()))
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)
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)
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)
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() ))
def redirectTo(URL: bytes, request: IRequest) -> bytes: """ Generate a redirect to the given location. @param URL: A L{bytes} giving the location to which to redirect. @param request: The request object to use to generate the redirect. @type request: L{IRequest<twisted.web.iweb.IRequest>} provider @raise TypeError: If the type of C{URL} a L{str} instead of L{bytes}. @return: A L{bytes} containing HTML which tries to convince the client agent to visit the new location even if it doesn't respect the I{FOUND} response code. This is intended to be returned from a render method, eg:: def render_GET(self, request): return redirectTo(b"http://example.com/", request) """ if not isinstance(URL, bytes): raise TypeError("URL must be bytes") request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.redirect(URL) # FIXME: The URL should be HTML-escaped. # https://twistedmatrix.com/trac/ticket/9839 content = b""" <html> <head> <meta http-equiv=\"refresh\" content=\"0;URL=%(url)s\"> </head> <body bgcolor=\"#FFFFFF\" text=\"#000000\"> <a href=\"%(url)s\">click here</a> </body> </html> """ % { b"url": URL } return content
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)
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)
async def newIncidentReportResource( self, request: IRequest, eventID: str ) -> KleinRenderable: """ New incident report endpoint. """ event = Event(id=eventID) del eventID await self.config.authProvider.authorizeRequest( request, event, Authorization.writeIncidentReports ) try: json = objectFromJSONBytesIO(request.content) except JSONDecodeError as e: return invalidJSONResponse(request, e) if json.get(IncidentReportJSONKey.event.value, event.id) != event.id: return badRequestResponse( "Event ID mismatch: " f"{json[IncidentReportJSONKey.event.value]} != {event.id}" ) if json.get(IncidentReportJSONKey.incidentNumber.value): return badRequestResponse( "New incident report may not be attached to an incident: " f"{json[IncidentReportJSONKey.incidentNumber.value]}" ) author = request.user.shortNames[0] now = DateTime.now(TimeZone.utc) jsonNow = jsonObjectFromModelObject(now) # Set JSON event id # 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.event.value] = event.id 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", str(incidentReport.number)) request.setHeader( HeaderName.location.value, f"{URLs.incidentNumber.asText()}/{incidentReport.number}", ) return noContentResponse(request)
async def newIncidentResource( self, request: IRequest, eventID: str ) -> KleinRenderable: """ New incident endpoint. """ event = Event(id=eventID) del 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", str(incident.number)) request.setHeader( HeaderName.location.value, f"{URLs.incidentNumber.asText()}/{incident.number}", ) return noContentResponse(request)
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)
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)