app: implement route prefix option
Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
3d9052d711
commit
ae1d3b0393
@ -168,6 +168,22 @@ class MoonrakerApp:
|
|||||||
self.key_path: pathlib.Path = self._get_path_option(
|
self.key_path: pathlib.Path = self._get_path_option(
|
||||||
config, 'ssl_key_path')
|
config, 'ssl_key_path')
|
||||||
|
|
||||||
|
# Route Prefix
|
||||||
|
home_pattern = "/"
|
||||||
|
self._route_prefix: str = ""
|
||||||
|
route_prefix = config.get("route_prefix", None)
|
||||||
|
if route_prefix is not None:
|
||||||
|
rparts = route_prefix.strip("/").split("/")
|
||||||
|
rp = "/".join(
|
||||||
|
[url_escape(part, plus=False) for part in rparts if part]
|
||||||
|
)
|
||||||
|
if not rp:
|
||||||
|
raise config.error(
|
||||||
|
f"Invalid value for option 'route_prefix': {route_prefix}"
|
||||||
|
)
|
||||||
|
self._route_prefix = f"/{rp}"
|
||||||
|
home_pattern = f"{self._route_prefix}/?"
|
||||||
|
|
||||||
# Set Up Websocket and Authorization Managers
|
# Set Up Websocket and Authorization Managers
|
||||||
self.wsm = WebsocketManager(self.server)
|
self.wsm = WebsocketManager(self.server)
|
||||||
self.internal_transport = InternalTransport(self.server)
|
self.internal_transport = InternalTransport(self.server)
|
||||||
@ -196,10 +212,10 @@ class MoonrakerApp:
|
|||||||
self.mutable_router = MutableRouter(self)
|
self.mutable_router = MutableRouter(self)
|
||||||
app_handlers: List[Any] = [
|
app_handlers: List[Any] = [
|
||||||
(AnyMatches(), self.mutable_router),
|
(AnyMatches(), self.mutable_router),
|
||||||
(r"/", WelcomeHandler),
|
(home_pattern, WelcomeHandler),
|
||||||
(r"/websocket", WebSocket),
|
(f"{self._route_prefix}/websocket", WebSocket),
|
||||||
(r"/klippysocket", BridgeSocket),
|
(f"{self._route_prefix}/klippysocket", BridgeSocket),
|
||||||
(r"/server/redirect", RedirectHandler)
|
(f"{self._route_prefix}/server/redirect", RedirectHandler)
|
||||||
]
|
]
|
||||||
self.app = tornado.web.Application(app_handlers, **app_args)
|
self.app = tornado.web.Application(app_handlers, **app_args)
|
||||||
self.get_handler_delegate = self.app.get_handler_delegate
|
self.get_handler_delegate = self.app.get_handler_delegate
|
||||||
@ -243,6 +259,15 @@ class MoonrakerApp:
|
|||||||
)
|
)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@property
|
||||||
|
def route_prefix(self):
|
||||||
|
return self._route_prefix
|
||||||
|
|
||||||
|
def parse_endpoint(self, http_path: str) -> str:
|
||||||
|
if not self._route_prefix or not http_path.startswith(self._route_prefix):
|
||||||
|
return http_path
|
||||||
|
return http_path[len(self._route_prefix):]
|
||||||
|
|
||||||
def listen(self, host: str, port: int, ssl_port: int) -> None:
|
def listen(self, host: str, port: int, ssl_port: int) -> None:
|
||||||
if host.lower() == "all":
|
if host.lower() == "all":
|
||||||
host = ""
|
host = ""
|
||||||
@ -317,7 +342,8 @@ class MoonrakerApp:
|
|||||||
params['callback'] = api_def.endpoint
|
params['callback'] = api_def.endpoint
|
||||||
params['need_object_parser'] = api_def.need_object_parser
|
params['need_object_parser'] = api_def.need_object_parser
|
||||||
self.mutable_router.add_handler(
|
self.mutable_router.add_handler(
|
||||||
api_def.uri, DynamicRequestHandler, params)
|
f"{self._route_prefix}{api_def.uri}", DynamicRequestHandler, params
|
||||||
|
)
|
||||||
self.registered_base_handlers.append(api_def.uri)
|
self.registered_base_handlers.append(api_def.uri)
|
||||||
for name, transport in self.api_transports.items():
|
for name, transport in self.api_transports.items():
|
||||||
transport.register_api_handler(api_def)
|
transport.register_api_handler(api_def)
|
||||||
@ -345,7 +371,9 @@ class MoonrakerApp:
|
|||||||
params['wrap_result'] = wrap_result
|
params['wrap_result'] = wrap_result
|
||||||
params['is_remote'] = False
|
params['is_remote'] = False
|
||||||
params['content_type'] = content_type
|
params['content_type'] = content_type
|
||||||
self.mutable_router.add_handler(uri, DynamicRequestHandler, params)
|
self.mutable_router.add_handler(
|
||||||
|
f"{self._route_prefix}{uri}", DynamicRequestHandler, params
|
||||||
|
)
|
||||||
self.registered_base_handlers.append(uri)
|
self.registered_base_handlers.append(uri)
|
||||||
for name, transport in self.api_transports.items():
|
for name, transport in self.api_transports.items():
|
||||||
if name in transports:
|
if name in transports:
|
||||||
@ -367,15 +395,21 @@ class MoonrakerApp:
|
|||||||
return
|
return
|
||||||
logging.debug(f"Registering static file: ({pattern}) {file_path}")
|
logging.debug(f"Registering static file: ({pattern}) {file_path}")
|
||||||
params = {'path': file_path}
|
params = {'path': file_path}
|
||||||
self.mutable_router.add_handler(pattern, FileRequestHandler, params)
|
self.mutable_router.add_handler(
|
||||||
|
f"{self._route_prefix}{pattern}", FileRequestHandler, params
|
||||||
|
)
|
||||||
|
|
||||||
def register_upload_handler(
|
def register_upload_handler(
|
||||||
self, pattern: str, location_prefix: Optional[str] = None
|
self, pattern: str, location_prefix: str = "server/files"
|
||||||
) -> None:
|
) -> None:
|
||||||
params: Dict[str, Any] = {'max_upload_size': self.max_upload_size}
|
params: Dict[str, Any] = {'max_upload_size': self.max_upload_size}
|
||||||
if location_prefix is not None:
|
location_prefix = location_prefix.strip("/")
|
||||||
params['location_prefix'] = location_prefix
|
if self._route_prefix:
|
||||||
self.mutable_router.add_handler(pattern, FileUploadHandler, params)
|
location_prefix = f"{self._route_prefix.strip('/')}/{location_prefix}"
|
||||||
|
params['location_prefix'] = location_prefix
|
||||||
|
self.mutable_router.add_handler(
|
||||||
|
f"{self._route_prefix}{pattern}", FileUploadHandler, params
|
||||||
|
)
|
||||||
|
|
||||||
def register_debug_handler(
|
def register_debug_handler(
|
||||||
self,
|
self,
|
||||||
@ -461,6 +495,7 @@ class MoonrakerApp:
|
|||||||
class AuthorizedRequestHandler(tornado.web.RequestHandler):
|
class AuthorizedRequestHandler(tornado.web.RequestHandler):
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self.server: Server = self.settings['server']
|
self.server: Server = self.settings['server']
|
||||||
|
self.endpoint: str = ""
|
||||||
|
|
||||||
def set_default_headers(self) -> None:
|
def set_default_headers(self) -> None:
|
||||||
origin: Optional[str] = self.request.headers.get("Origin")
|
origin: Optional[str] = self.request.headers.get("Origin")
|
||||||
@ -473,9 +508,11 @@ class AuthorizedRequestHandler(tornado.web.RequestHandler):
|
|||||||
self.cors_enabled = auth.check_cors(origin, self)
|
self.cors_enabled = auth.check_cors(origin, self)
|
||||||
|
|
||||||
def prepare(self) -> None:
|
def prepare(self) -> None:
|
||||||
|
app: MoonrakerApp = self.server.lookup_component("application")
|
||||||
|
self.endpoint = app.parse_endpoint(self.request.path or "")
|
||||||
auth: AuthComp = self.server.lookup_component('authorization', None)
|
auth: AuthComp = self.server.lookup_component('authorization', None)
|
||||||
if auth is not None:
|
if auth is not None:
|
||||||
self.current_user = auth.check_authorized(self.request)
|
self.current_user = auth.check_authorized(self.request, self.endpoint)
|
||||||
|
|
||||||
def options(self, *args, **kwargs) -> None:
|
def options(self, *args, **kwargs) -> None:
|
||||||
# Enable CORS if configured
|
# Enable CORS if configured
|
||||||
@ -519,6 +556,7 @@ class AuthorizedFileHandler(tornado.web.StaticFileHandler):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super(AuthorizedFileHandler, self).initialize(path, default_filename)
|
super(AuthorizedFileHandler, self).initialize(path, default_filename)
|
||||||
self.server: Server = self.settings['server']
|
self.server: Server = self.settings['server']
|
||||||
|
self.endpoint: str = ""
|
||||||
|
|
||||||
def set_default_headers(self) -> None:
|
def set_default_headers(self) -> None:
|
||||||
origin: Optional[str] = self.request.headers.get("Origin")
|
origin: Optional[str] = self.request.headers.get("Origin")
|
||||||
@ -531,9 +569,11 @@ class AuthorizedFileHandler(tornado.web.StaticFileHandler):
|
|||||||
self.cors_enabled = auth.check_cors(origin, self)
|
self.cors_enabled = auth.check_cors(origin, self)
|
||||||
|
|
||||||
def prepare(self) -> None:
|
def prepare(self) -> None:
|
||||||
|
app: MoonrakerApp = self.server.lookup_component("application")
|
||||||
|
self.endpoint = app.parse_endpoint(self.request.path or "")
|
||||||
auth: AuthComp = self.server.lookup_component('authorization', None)
|
auth: AuthComp = self.server.lookup_component('authorization', None)
|
||||||
if auth is not None and self._check_need_auth():
|
if auth is not None and self._check_need_auth():
|
||||||
self.current_user = auth.check_authorized(self.request)
|
self.current_user = auth.check_authorized(self.request, self.endpoint)
|
||||||
|
|
||||||
def options(self, *args, **kwargs) -> None:
|
def options(self, *args, **kwargs) -> None:
|
||||||
# Enable CORS if configured
|
# Enable CORS if configured
|
||||||
@ -645,8 +685,8 @@ class DynamicRequestHandler(AuthorizedRequestHandler):
|
|||||||
resp = args
|
resp = args
|
||||||
if isinstance(args, dict):
|
if isinstance(args, dict):
|
||||||
if (
|
if (
|
||||||
self.request.path.startswith("/access") or
|
self.endpoint.startswith("/access") or
|
||||||
self.request.path.startswith("/machine/sudo/password")
|
self.endpoint.startswith("/machine/sudo/password")
|
||||||
):
|
):
|
||||||
resp = {key: "<sanitized>" for key in args}
|
resp = {key: "<sanitized>" for key in args}
|
||||||
elif isinstance(args, str):
|
elif isinstance(args, str):
|
||||||
@ -669,7 +709,7 @@ class DynamicRequestHandler(AuthorizedRequestHandler):
|
|||||||
) -> Any:
|
) -> Any:
|
||||||
assert callable(self.callback)
|
assert callable(self.callback)
|
||||||
return await self.callback(
|
return await self.callback(
|
||||||
WebRequest(self.request.path, args, self.request.method,
|
WebRequest(self.endpoint, args, self.request.method,
|
||||||
conn=conn, ip_addr=self.request.remote_ip or "",
|
conn=conn, ip_addr=self.request.remote_ip or "",
|
||||||
user=self.current_user))
|
user=self.current_user))
|
||||||
|
|
||||||
@ -720,7 +760,7 @@ class FileRequestHandler(AuthorizedFileHandler):
|
|||||||
f"filename*=UTF-8\'\'{utf8_basename}")
|
f"filename*=UTF-8\'\'{utf8_basename}")
|
||||||
|
|
||||||
async def delete(self, path: str) -> None:
|
async def delete(self, path: str) -> None:
|
||||||
path = self.request.path.lstrip("/").split("/", 2)[-1]
|
path = self.endpoint.lstrip("/").split("/", 2)[-1]
|
||||||
path = url_unescape(path, plus=False)
|
path = url_unescape(path, plus=False)
|
||||||
file_manager: FileManager
|
file_manager: FileManager
|
||||||
file_manager = self.server.lookup_component('file_manager')
|
file_manager = self.server.lookup_component('file_manager')
|
||||||
|
@ -760,11 +760,11 @@ class Authorization:
|
|||||||
return False
|
return False
|
||||||
return self.failed_logins.get(ip_addr, 0) >= self.max_logins
|
return self.failed_logins.get(ip_addr, 0) >= self.max_logins
|
||||||
|
|
||||||
def check_authorized(self,
|
def check_authorized(
|
||||||
request: HTTPServerRequest
|
self, request: HTTPServerRequest, endpoint: str = "",
|
||||||
) -> Optional[Dict[str, Any]]:
|
) -> Optional[Dict[str, Any]]:
|
||||||
if (
|
if (
|
||||||
request.path in self.permitted_paths
|
endpoint in self.permitted_paths
|
||||||
or request.method == "OPTIONS"
|
or request.method == "OPTIONS"
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user