async def concentric_street_name_by_id(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON dictionary: concentric streets by ID. """ namesByID = await self.config.store.concentricStreets(self.event) return jsonTextFromObject(namesByID)
async def readIncidentResource( self, request: IRequest, eventID: str, number: int ) -> KleinRenderable: """ Incident endpoint. """ event = Event(id=eventID) await self.config.authProvider.authorizeRequest( request, event, Authorization.readIncidents ) try: number = int(number) except ValueError: return notFoundResponse(request) try: incident = await self.config.store.incidentWithNumber( event, number ) except NoSuchIncidentError: return notFoundResponse(request) data = ( jsonTextFromObject(jsonObjectFromModelObject(incident)) .encode("utf-8") ) return jsonBytes(request, data)
async def events_list(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON list of strings: events IDs. """ return jsonTextFromObject(e.id for e in await self.config.store.events())
async def eventsResource(self, request: IRequest) -> KleinRenderable: """ Events endpoint. """ self.config.authProvider.authenticateRequest(request) authorizationsForUser = partial( self.config.authProvider.authorizationsForUser, request.user ) events = sorted( [ event for event in await self.config.store.events() if Authorization.readIncidents & await authorizationsForUser(event) ] ) stream = buildJSONArray( jsonTextFromObject(event).encode("utf-8") for event in events ) writeJSONStream(request, stream, None) return None
async def readIncidentResource( self, request: IRequest, eventID: str, number: str ) -> KleinRenderable: """ Incident endpoint. """ event = Event(id=eventID) del eventID await self.config.authProvider.authorizeRequest( request, event, Authorization.readIncidents ) try: incidentNumber = int(number) except ValueError: return notFoundResponse(request) del number try: incident = await self.config.store.incidentWithNumber( event, incidentNumber ) except NoSuchIncidentError: return notFoundResponse(request) data = jsonTextFromObject(jsonObjectFromModelObject(incident)).encode( "utf-8" ) return jsonBytes(request, data)
async def readIncidentReportResource( self, request: IRequest, eventID: str, number: str ) -> KleinRenderable: """ Incident report endpoint. """ try: incidentReportNumber = int(number) except ValueError: self.config.authProvider.authenticateRequest(request) return notFoundResponse(request) del number event = Event(id=eventID) del eventID incidentReport = await self.config.store.incidentReportWithNumber( event, incidentReportNumber ) await self.config.authProvider.authorizeRequestForIncidentReport( request, incidentReport ) text = jsonTextFromObject(jsonObjectFromModelObject(incidentReport)) return jsonBytes(request, text.encode("utf-8"))
async def asText(self) -> str: """ Export data store as text. """ json = await self.asJSON() self._log.info("Encoding exported data as JSON text...") return jsonTextFromObject(json)
def incident_report_number( self, request: IRequest, tag: Tag ) -> KleinRenderable: """ JSON integer: incident report number. """ return jsonTextFromObject(self.number)
async def concentric_street_name_by_id( self, request: IRequest, tag: Tag ) -> KleinRenderable: """ JSON dictionary: concentric streets by ID. """ namesByID = await self.config.store.concentricStreets(self.event) return jsonTextFromObject(namesByID)
def incidents_url(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON string: URL for incidents endpoint for the event. """ return jsonTextFromObject( self.config.urls.incidents.asText() .replace("<eventID>", self.event.id) )
async def listIncidentReportsResource( self, request: IRequest ) -> KleinRenderable: """ Incident reports endpoint. """ store = self.config.store eventID = queryValue(request, "event") incidentNumberText = queryValue(request, "incident") if eventID is None: return invalidQueryResponse(request, "event") if eventID == incidentNumberText == "": await self.config.authProvider.authorizeRequest( request, None, Authorization.readIncidentReports ) incidentReports = await store.detachedIncidentReports() else: try: event = Event(id=eventID) except ValueError: return invalidQueryResponse( request, "event", eventID ) await self.config.authProvider.authorizeRequest( request, event, Authorization.readIncidents ) if incidentNumberText is None: incidentReports = await store.incidentReports(event=event) else: try: incidentNumber = int(incidentNumberText) except ValueError: return invalidQueryResponse( request, "incident", incidentNumberText ) incidentReports = ( await store.incidentReportsAttachedToIncident( event=event, incidentNumber=incidentNumber ) ) stream = buildJSONArray( jsonTextFromObject( jsonObjectFromModelObject(incidentReport) ).encode("utf-8") for incidentReport in incidentReports ) writeJSONStream(request, stream, None) return None
async def listIncidentReportsResource( self, request: IRequest, eventID: str ) -> KleinRenderable: """ Incident reports endpoint. """ event = Event(id=eventID) del eventID try: await self.config.authProvider.authorizeRequest( request, event, Authorization.readIncidents ) limitedAccess = False except NotAuthorizedError: await self.config.authProvider.authorizeRequest( request, event, Authorization.writeIncidentReports ) limitedAccess = True incidentNumberText = queryValue(request, "incident") store = self.config.store incidentReports: Iterable[IncidentReport] if limitedAccess: incidentReports = ( incidentReport for incidentReport in await store.incidentReports(event=event) if request.user.rangerHandle in (entry.author for entry in incidentReport.reportEntries) ) elif incidentNumberText is None: incidentReports = await store.incidentReports(event=event) else: try: incidentNumber = int(incidentNumberText) except ValueError: return invalidQueryResponse( request, "incident", incidentNumberText ) incidentReports = await store.incidentReportsAttachedToIncident( event=event, incidentNumber=incidentNumber ) stream = buildJSONArray( jsonTextFromObject( jsonObjectFromModelObject(incidentReport) ).encode("utf-8") for incidentReport in incidentReports ) writeJSONStream(request, stream, None) return None
async def readStreetsResource(self, request: IRequest) -> KleinRenderable: """ Street list endpoint. """ await self.config.authProvider.authorizeRequest( request, None, Authorization.imsAdmin) store = self.config.store streets = {} for event in await store.events(): streets[event.id] = await store.concentricStreets(event) return jsonTextFromObject(streets)
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 readStreetsResource(self, request: IRequest) -> KleinRenderable: """ Street list endpoint. """ await self.config.authProvider.authorizeRequest( request, None, Authorization.imsAdmin ) store = self.config.store streets = {} for event in await store.events(): streets[event.id] = await store.concentricStreets(event) return jsonTextFromObject(streets)
async def listIncidentsResource(self, request: IRequest, eventID: str) -> None: """ Incident list endpoint. """ event = Event(id=eventID) await self.config.authProvider.authorizeRequest( request, event, Authorization.readIncidents) stream = buildJSONArray( jsonTextFromObject(jsonObjectFromModelObject(incident)).encode( "utf-8") for incident in await self.config.store.incidents(event)) writeJSONStream(request, stream, None)
async def incidentTypesResource(self, request: IRequest) -> None: """ Incident types endpoint. """ self.config.authProvider.authenticateRequest(request) hidden = queryValue(request, "hidden") == "true" incidentTypes = tuple( await self.config.store.incidentTypes(includeHidden=hidden)) stream = buildJSONArray( jsonTextFromObject(incidentType).encode("utf-8") for incidentType in incidentTypes) writeJSONStream(request, stream, None)
async def personnelData(self) -> Tuple[Iterable[bytes], str]: """ Data for personnel endpoint. """ try: personnel = await self.config.dms.personnel() except DMSError as e: self._log.error("Unable to vend personnel: {failure}", failure=e) personnel = () return ( buildJSONArray( jsonTextFromObject(jsonObjectFromModelObject(ranger)).encode( "utf-8") for ranger in personnel), str(hash(personnel)), )
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 readAdminAccessResource(self, request: IRequest) -> KleinRenderable: """ Admin access control endpoint. """ await self.config.authProvider.authorizeRequest( request, None, Authorization.imsAdmin) store = self.config.store acl = {} for event in await store.events(): acl[event.id] = dict( readers=await store.readers(event), writers=await store.writers(event), ) return jsonTextFromObject(acl)
async def listIncidentReportsResource( self, request: IRequest) -> KleinRenderable: """ Incident reports endpoint. """ store = self.config.store eventID = queryValue(request, "event") incidentNumberText = queryValue(request, "incident") if eventID is None: return invalidQueryResponse(request, "event") if incidentNumberText is None: return invalidQueryResponse(request, "incident") if eventID == incidentNumberText == "": await self.config.authProvider.authorizeRequest( request, None, Authorization.readIncidentReports) incidentReports = await store.detachedIncidentReports() else: try: event = Event(id=eventID) except ValueError: return invalidQueryResponse(request, "event", eventID) try: incidentNumber = int(incidentNumberText) except ValueError: return invalidQueryResponse(request, "incident", incidentNumberText) await self.config.authProvider.authorizeRequest( request, event, Authorization.readIncidents) incidentReports = await store.incidentReportsAttachedToIncident( event=event, incidentNumber=incidentNumber) stream = buildJSONArray( jsonTextFromObject(jsonObjectFromModelObject( incidentReport)).encode("utf-8") for incidentReport in incidentReports) writeJSONStream(request, stream, None) return None
def _transmogrify(self, loggerEvent: Mapping, eventID: int) -> Optional[Event]: """ Convert a logger event into an EventSource event. """ eventClass = loggerEvent.get("storeWriteClass", None) if eventClass is None: # Not a data store event return None elif eventClass is Incident: incident = loggerEvent.get("incident", None) if incident is None: incidentNumber = loggerEvent.get("incidentNumber", None) else: incidentNumber = incident.number if incidentNumber is None: self._log.critical( "Unable to determine incident number from store event: " "{event}", event=loggerEvent, ) return None message = dict(incident_number=incidentNumber) else: self._log.critical( "Unknown data store event class {eventClass} " "sent event: {event}", eventClass=eventClass, event=loggerEvent, ) return None eventSourceEvent = Event( eventID=eventID, eventClass=eventClass.__name__, message=jsonTextFromObject(message), ) return eventSourceEvent
def _transmogrify( self, loggerEvent: Mapping, eventID: int ) -> Optional[Event]: """ Convert a logger event into an EventSource event. """ eventClass = loggerEvent.get("storeWriteClass", None) if eventClass is None: # Not a data store event return None elif eventClass is Incident: incident = loggerEvent.get("incident", None) if incident is None: incidentNumber = loggerEvent.get("incidentNumber", None) else: incidentNumber = incident.number if incidentNumber is None: self.log.critical( "Unable to determine incident number from store event: " "{event}", event=loggerEvent, ) return None message = dict(incident_number=incidentNumber) else: self.log.debug( "Unknown data store event class: {eventClass}", eventClass=eventClass ) return None eventSourceEvent = Event( eventID=eventID, eventClass=eventClass.__name__, message=jsonTextFromObject(message), ) return eventSourceEvent
async def personnelData(self) -> Tuple[Iterable[bytes], str]: """ Data for personnel endpoint. """ try: personnel = await self.config.dms.personnel() except DMSError as e: self._log.error("Unable to vend personnel: {failure}", failure=e) personnel = () return ( buildJSONArray( jsonTextFromObject( jsonObjectFromModelObject(ranger) ).encode("utf-8") for ranger in personnel ), str(hash(personnel)), )
async def readAdminAccessResource( self, request: IRequest ) -> KleinRenderable: """ Admin access control endpoint. """ await self.config.authProvider.authorizeRequest( request, None, Authorization.imsAdmin ) store = self.config.store acl = {} for event in await store.events(): acl[event.id] = dict( readers=await store.readers(event), writers=await store.writers(event), ) return jsonTextFromObject(acl)
async def incidentTypesResource( self, request: IRequest ) -> KleinRenderable: """ Incident types endpoint. """ self.config.authProvider.authenticateRequest(request) hidden = queryValue(request, "hidden") == "true" incidentTypes = tuple( await self.config.store.incidentTypes(includeHidden=hidden) ) stream = buildJSONArray( jsonTextFromObject(incidentType).encode("utf-8") for incidentType in incidentTypes ) writeJSONStream(request, stream, None) return None
async def listIncidentsResource( self, request: IRequest, eventID: str ) -> None: """ Incident list endpoint. """ event = Event(id=eventID) await self.config.authProvider.authorizeRequest( request, event, Authorization.readIncidents ) stream = buildJSONArray( jsonTextFromObject( jsonObjectFromModelObject(incident) ).encode("utf-8") for incident in await self.config.store.incidents(event) ) writeJSONStream(request, stream, None) return None
def url(self, request: IRequest, tag: Tag) -> KleinRenderable: """ Look up a URL with the name specified by the given tag's C{"url"} attribute, which will be removed. If the tag has an C{"attr"} attribute, remove it and add the URL to the tag in the attribute named by the (removed) C{"attr"} attribute and return the tag. For C{"a"} tags, C{"attr"} defaults to C{"href"}. For C{"img"} tags, C{"attr"} defaults to C{"src"}. If the C{"attr"} attribute is defined C{""}, return the URL as text. """ name = tag.attributes.pop("url", None) if name is None: raise ValueError("Rendered URL must have a url attribute") try: url = getattr(self.config.urls, name) except AttributeError: raise ValueError(f"Unknown URL name: {name}") text = url.asText() if tag.tagName == "json": return jsonTextFromObject(text) attributeName = tag.attributes.pop("attr", None) if attributeName is None: if tag.tagName in ("a", "link"): attributeName = "href" elif tag.tagName in ("script", "img"): attributeName = "src" else: raise ValueError("Rendered URL must have an attr attribute") if attributeName == "": return text else: tag.attributes[attributeName] = text return tag
async def eventsResource(self, request: IRequest) -> KleinRenderable: """ Events endpoint. """ self.config.authProvider.authenticateRequest(request) authorizationsForUser = partial( self.config.authProvider.authorizationsForUser, request.user ) events = sorted([ event for event in await self.config.store.events() if Authorization.readIncidents & await authorizationsForUser(event) ]) stream = buildJSONArray( jsonTextFromObject(event).encode("utf-8") for event in events ) writeJSONStream(request, stream, None) return None
async def readIncidentReportResource( self, request: IRequest, number: int ) -> KleinRenderable: """ Incident report endpoint. """ try: number = int(number) except ValueError: self.config.authProvider.authenticateRequest(request) return notFoundResponse(request) incidentReport = await self.config.store.incidentReportWithNumber( number ) await self.config.authProvider.authorizeRequestForIncidentReport( request, incidentReport ) text = jsonTextFromObject(jsonObjectFromModelObject(incidentReport)) return jsonBytes(request, text.encode("utf-8"))
""" name: str = title number: Optional[int] @renderer def editing_allowed(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON boolean, true if editing is allowed. """ if (request.authorizations & Authorization.writeIncidentReports): return jsonTrue else: return jsonFalse @renderer def incident_report_number( self, request: IRequest, tag: Tag ) -> KleinRenderable: """ JSON integer: incident report number. """ return jsonTextFromObject(self.number) jsonTrue = jsonTextFromObject(True) jsonFalse = jsonTextFromObject(False)
def load(self) -> None: """ Load the configuration. """ command = basename(argv[0]) configParser = ConfigParser() def readConfig(path: Optional[Path]) -> None: if path is None: self._log.info("No configuration file specified.") return for _okFile in configParser.read(str(path)): self._log.info( "Read configuration file: {path}", path=path ) break else: self._log.error( "Unable to read configuration file: {path}", path=path ) def valueFromConfig( section: str, option: str, default: Optional[str] ) -> Optional[str]: try: value = configParser.get(section, option) if value: return value else: return default except (NoSectionError, NoOptionError): return default def pathFromConfig( section: str, option: str, root: Path, segments: Tuple[str] ) -> Path: if section is None: text = None else: text = valueFromConfig(section, option, None) if text is None: path = root for segment in segments: path = path / segment elif text.startswith("/"): path = Path(text) else: path = root for segment in text.split(pathsep): path = path / segment return path readConfig(self.ConfigFile) if self.ConfigFile is None: defaultRoot = Path(getcwd()) else: defaultRoot = self.ConfigFile.parent.parent self.HostName = valueFromConfig("Core", "Host", "localhost") self._log.info("HostName: {hostName}", hostName=self.HostName) self.Port = int(cast(str, valueFromConfig("Core", "Port", "8080"))) self._log.info("Port: {port}", port=self.Port) self.ServerRoot = pathFromConfig( "Core", "ServerRoot", defaultRoot, cast(Tuple[str], ()) ) self._log.info("Server root: {path}", path=self.ServerRoot) self.ConfigRoot = pathFromConfig( "Core", "ConfigRoot", self.ServerRoot, ("conf",) ) self._log.info("Config root: {path}", path=self.ConfigRoot) self.DataRoot = pathFromConfig( "Core", "DataRoot", self.ServerRoot, ("data",) ) self._log.info("Data root: {path}", path=self.DataRoot) self.DatabasePath = pathFromConfig( "Core", "Database", self.DataRoot, ("db.sqlite",) ) self._log.info("Database: {path}", path=self.DatabasePath) self.CachedResourcesPath = pathFromConfig( "Core", "CachedResources", self.DataRoot, ("cache",) ) self._log.info( "CachedResourcesPath: {path}", path=self.CachedResourcesPath ) self.LogLevelName = valueFromConfig("Core", "LogLevel", "info") self._log.info("LogLevel: {logLevel}", logLevel=self.LogLevelName) self.LogFormat = valueFromConfig("Core", "LogFormat", "text") self._log.info("LogFormat: {logFormat}", logFormat=self.LogFormat) self.LogFilePath = pathFromConfig( "Core", "LogFile", self.DataRoot, (f"{command}.log",) ) self._log.info("LogFile: {path}", path=self.LogFilePath) admins = cast(str, valueFromConfig("Core", "Admins", "")) self.IMSAdmins: FrozenSet[str] = frozenset( a.strip() for a in admins.split(",") ) self._log.info("Admins: {admins}", admins=self.IMSAdmins) active = ( cast(str, valueFromConfig("Core", "RequireActive", "true")).lower() ) if active in ("false", "no", "0"): self.RequireActive = False else: self.RequireActive = True self._log.info( "RequireActive: {active}", active=self.RequireActive ) self.DMSHost = valueFromConfig("DMS", "Hostname", None) self.DMSDatabase = valueFromConfig("DMS", "Database", None) self.DMSUsername = valueFromConfig("DMS", "Username", None) self.DMSPassword = valueFromConfig("DMS", "Password", None) self._log.info( "Database: {user}@{host}/{db}", user=self.DMSUsername, host=self.DMSHost, db=self.DMSDatabase, ) self.MasterKey = valueFromConfig("Core", "MasterKey", None) # # Persist some objects # self.dms = DutyManagementSystem( host=self.DMSHost, database=self.DMSDatabase, username=self.DMSUsername, password=self.DMSPassword, ) self.store: IMSDataStore = DataStore(dbPath=self.DatabasePath) self.authProvider = AuthProvider( store=self.store, dms=self.dms, requireActive=self.RequireActive, adminUsers=self.IMSAdmins, masterKey=self.MasterKey, ) locationsPath = self.DataRoot / "locations.json" if locationsPath.is_file(): with locationsPath.open() as jsonStrem: json = objectFromJSONBytesIO(jsonStrem) self._log.info("{count} locations", count=len(json)) self.locationsJSONBytes = jsonTextFromObject(json).encode("utf-8") else: self._log.info("No locations file: {path}", path=locationsPath) self.locationsJSONBytes = jsonTextFromObject([]).encode("utf-8")
def event_id(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON string: event ID. """ return jsonTextFromObject(self.event.id)
class IncidentReportPage(Page): """ Incident report page. """ def __init__(self, config: Configuration, number: Optional[int]) -> None: super().__init__(config=config, title=title) self.number = number @renderer def editing_allowed(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON boolean, true if editing is allowed. """ if (request.authorizations & Authorization.writeIncidentReports): return jsonTrue else: return jsonFalse @renderer def incident_report_number(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON integer: incident report number. """ return jsonTextFromObject(self.number) jsonTrue = jsonTextFromObject(True) jsonFalse = jsonTextFromObject(False)
def incident_number(self, request: IRequest, tag: Tag) -> KleinRenderable: """ JSON integer: incident number. """ return jsonTextFromObject(self.number)