ovenemprex/management.py
Trysdyn Black a274d1a0f6 Report cached stream list in management
The stream list caching issues should be fixed, but this is useful for
sniffing out possible future issues.
2025-03-07 22:10:42 -08:00

139 lines
5.4 KiB
Python

import subprocess
from pathlib import Path
from urllib.parse import urlparse
import cherrypy
from mako.template import Template
import config
import ovenapi
class Management:
def __init__(self):
self.page_template = Path("template/management.mako").read_text(encoding="utf-8")
self.redirect_template = Path("template/message.mako").read_text(encoding="utf-8")
self.api = ovenapi.get_api_handle(username=config.API_USER, password=config.API_PASS)
@staticmethod
def __verify_same_domain() -> bool:
"""
Verify that the requested domain and referer domain match.
This is mainly intended to be used as a guard for "destructive" endpoints such as
/management/restart. This safeguards against cross-site requests as well as accidental
history completions such as intents to access /management but one's browser helpfully
populates /management/restart.
"""
referer = cherrypy.request.headers.get("Referer", "").lower()
# For comparing request and referer domains we drop the port
referer_domain = urlparse(referer).netloc.split(":")[0]
request_domain = urlparse(cherrypy.request.base).netloc.split(":")[0]
return referer_domain == request_domain
@staticmethod
def __restart_server() -> None:
subprocess.call(["sudo", "/usr/bin/systemctl", "restart", "ovenmediaengine"])
def __message_and_redirect(self, message: str) -> bytes | str:
return Template(self.redirect_template).render(message=message)
@cherrypy.expose
def restart(self) -> bytes | str:
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
# Blank our stream list because we're about to DC everyone
config.LAST_STREAM_LIST = []
self.__restart_server()
# Compile and dispatch our response
return self.__message_and_redirect("Server restarted")
@cherrypy.expose
def disconnect(self, target):
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
vhost, app, stream = target.split(":")
self.api.disconnect_key(vhost, app, stream)
return self.__message_and_redirect(f"Disconnected {target}")
@cherrypy.expose
def ban(self, target):
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
vhost, app, stream = target.split(":")
ip = self.api.get_stream_ip(vhost, app, stream)
if ip:
config.BLOCKED_IPS.append(ip)
self.disconnect(target)
return self.__message_and_redirect(f"Banned {ip}")
return self.__message_and_redirect("No stream found at that location or other error")
@cherrypy.expose
def unban(self, target):
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
if target in config.BLOCKED_IPS:
config.BLOCKED_IPS.remove(target)
return self.__message_and_redirect(f"Unbanned {target}")
return self.__message_and_redirect(f"{target} not in ban list")
@cherrypy.expose
def disable(self, target):
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
config.DISABLED_KEYS.append(target)
self.disconnect(target)
return self.__message_and_redirect(f"Disabled key {target}")
@cherrypy.expose
def enable(self, target):
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
if target in config.DISABLED_KEYS:
config.DISABLED_KEYS.remove(target)
return self.__message_and_redirect(f"Re-enabled {target}")
return self.__message_and_redirect(f"{target} not in disabled key list")
@cherrypy.expose
def stop(self):
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
config.DISABLED = True
self.api.disconnect_all()
return self.__message_and_redirect("Server disabled")
@cherrypy.expose
def start(self):
if not self.__verify_same_domain():
cherrypy.response.status = 403
return "Cross-site request detected. Please go back to /management and try again"
config.DISABLED = False
return self.__message_and_redirect("Server re-enabled")
@cherrypy.expose
def default(self) -> bytes | str:
if not (config.API_USER and config.API_PASS):
cherrypy.response.status = 503
return "Remote management is disabled on this node."
data = self.api.get_all_stream_info()
return Template(self.page_template).render(
DISABLED=config.DISABLED,
BLOCKED_IPS=config.BLOCKED_IPS,
DISABLED_KEYS=config.DISABLED_KEYS,
STREAM_LIST=config.LAST_STREAM_LIST,
data=data,
)