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 _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
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()
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
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 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 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")
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 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")
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."
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")
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 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")
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")
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
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 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 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)
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)
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")
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")
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, )
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 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")
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)
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)
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, )
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 logout(self, request: IRequest) -> KleinRenderable: """ Endpoint for logging out. """ session = request.getSession() session.expire() # Redirect back to application home return redirect(request, URLs.app)
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")
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 )
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()
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)
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
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)
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)
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)