Exemple #1
0
 def load_plugin(self, config, plugin_name, default=Sentinel):
     if plugin_name in self.plugins:
         return self.plugins[plugin_name]
     # Make sure plugin exists
     mod_path = os.path.join(
         os.path.dirname(__file__), 'plugins', plugin_name + '.py')
     if not os.path.exists(mod_path):
         msg = f"Plugin ({plugin_name}) does not exist"
         logging.info(msg)
         if default == Sentinel:
             raise ServerError(msg)
         return default
     module = importlib.import_module("plugins." + plugin_name)
     try:
         if plugin_name not in CORE_PLUGINS:
             config = config[plugin_name]
         load_func = getattr(module, "load_plugin")
         plugin = load_func(config)
     except Exception:
         msg = f"Unable to load plugin ({plugin_name})"
         logging.exception(msg)
         if default == Sentinel:
             raise ServerError(msg)
         return default
     self.plugins[plugin_name] = plugin
     logging.info(f"Plugin ({plugin_name}) loaded")
     return plugin
Exemple #2
0
 def load_component(self, config, component_name, default=Sentinel):
     if component_name in self.components:
         return self.components[component_name]
     # Make sure component exists
     mod_path = os.path.join(os.path.dirname(__file__), 'components',
                             component_name + '.py')
     if not os.path.exists(mod_path):
         msg = f"Component ({component_name}) does not exist"
         logging.info(msg)
         self.failed_components.append(component_name)
         if default == Sentinel:
             raise ServerError(msg)
         return default
     try:
         module = importlib.import_module("components." + component_name)
         func_name = "load_component"
         if hasattr(module, "load_component_multi"):
             func_name = "load_component_multi"
         if component_name not in CORE_COMPONENTS and \
                 func_name == "load_component":
             config = config[component_name]
         load_func = getattr(module, func_name)
         component = load_func(config)
     except Exception:
         msg = f"Unable to load component: ({component_name})"
         logging.exception(msg)
         self.failed_components.append(component_name)
         if default == Sentinel:
             raise ServerError(msg)
         return default
     self.components[component_name] = component
     logging.info(f"Component ({component_name}) loaded")
     return component
Exemple #3
0
 async def send_request(self, request):
     if self.iostream is None:
         request.notify(ServerError("Klippy Host not connected", 503))
         return
     data = json.dumps(request.to_dict()).encode() + b"\x03"
     try:
         await self.iostream.write(data)
     except iostream.StreamClosedError:
         request.notify(ServerError("Klippy Host not connected", 503))
Exemple #4
0
 async def send_request(self, request: BaseRequest) -> None:
     if self.writer is None:
         request.notify(ServerError("Klippy Host not connected", 503))
         return
     data = json.dumps(request.to_dict()).encode() + b"\x03"
     try:
         self.writer.write(data)
         await self.writer.drain()
     except Exception:
         request.notify(ServerError("Klippy Host not connected", 503))
         await self.close()
Exemple #5
0
 def get(self,
         key: str,
         default: Union[SentinelClass, _T] = SENTINEL) -> Union[_T, Any]:
     val = self.args.get(key, default)
     if isinstance(val, SentinelClass):
         raise ServerError(f"No data for argument: {key}")
     return val
Exemple #6
0
 def process_command(self, cmd):
     method = cmd.get('method', None)
     if method is not None:
         # This is a remote method called from klippy
         cb = self.remote_methods.get(method, None)
         if cb is not None:
             params = cmd.get('params', {})
             cb(**params)
         else:
             logging.info(f"Unknown method received: {method}")
         return
     # This is a response to a request, process
     req_id = cmd.get('id', None)
     request = self.pending_requests.pop(req_id, None)
     if request is None:
         logging.info(
             f"No request matching request ID: {req_id}, "
             f"response: {cmd}")
         return
     if 'result' in cmd:
         result = cmd['result']
         if not result:
             result = "ok"
     else:
         err = cmd.get('error', "Malformed Klippy Response")
         result = ServerError(err, 400)
     request.notify(result)
 def load_component(
         self,
         config: confighelper.ConfigHelper,
         component_name: str,
         default: Union[SentinelClass, _T] = SENTINEL) -> Union[_T, Any]:
     if component_name in self.components:
         return self.components[component_name]
     try:
         module = importlib.import_module("components." + component_name)
         func_name = "load_component"
         if hasattr(module, "load_component_multi"):
             func_name = "load_component_multi"
         if component_name not in CORE_COMPONENTS and \
                 func_name == "load_component":
             config = config[component_name]
         load_func = getattr(module, func_name)
         component = load_func(config)
     except Exception:
         msg = f"Unable to load component: ({component_name})"
         logging.exception(msg)
         self.failed_components.append(component_name)
         if isinstance(default, SentinelClass):
             raise ServerError(msg)
         return default
     self.components[component_name] = component
     logging.info(f"Component ({component_name}) loaded")
     return component
Exemple #8
0
 def load_component(
         self,
         config: confighelper.ConfigHelper,
         component_name: str,
         default: Union[SentinelClass, _T] = SENTINEL) -> Union[_T, Any]:
     if component_name in self.components:
         return self.components[component_name]
     try:
         module = importlib.import_module("components." + component_name)
         is_core = component_name in CORE_COMPONENTS
         fallback: Optional[str] = "server" if is_core else None
         config = config.getsection(component_name, fallback)
         load_func = getattr(module, "load_component")
         component = load_func(config)
     except Exception:
         msg = f"Unable to load component: ({component_name})"
         logging.exception(msg)
         if component_name not in self.failed_components:
             self.failed_components.append(component_name)
         if isinstance(default, SentinelClass):
             raise ServerError(msg)
         return default
     self.components[component_name] = component
     logging.info(f"Component ({component_name}) loaded")
     return component
Exemple #9
0
 def process_command(self, cmd: Dict[str, Any]) -> None:
     method = cmd.get('method', None)
     if method is not None:
         # This is a remote method called from klippy
         if method in self.remote_methods:
             params = cmd.get('params', {})
             self.event_loop.register_callback(
                 self._execute_method, method, **params)
         else:
             logging.info(f"Unknown method received: {method}")
         return
     # This is a response to a request, process
     req_id = cmd.get('id', None)
     request: Optional[BaseRequest]
     request = self.pending_requests.pop(req_id, None)
     if request is None:
         logging.info(
             f"No request matching request ID: {req_id}, "
             f"response: {cmd}")
         return
     if 'result' in cmd:
         result = cmd['result']
         if not result:
             result = "ok"
     else:
         err = cmd.get('error', "Malformed Klippy Response")
         result = ServerError(err, 400)
     request.notify(result)
Exemple #10
0
 def lookup_component(self,
                      component_name: str,
                      default: Union[SentinelClass, _T] = SENTINEL
                      ) -> Union[_T, Any]:
     component = self.components.get(component_name, default)
     if isinstance(component, SentinelClass):
         raise ServerError(f"Component ({component_name}) not found")
     return component
Exemple #11
0
 def _handle_klippy_response(self, request_id, response):
     req = self.pending_requests.pop(request_id, None)
     if req is not None:
         if isinstance(response, dict) and 'error' in response:
             response = ServerError(response['message'], 400)
         req.notify(response)
     else:
         logging.info("No request matching response: " + str(response))
Exemple #12
0
    def __init__(self, config):
        api_key_file = config.get('api_key_file', "~/.moonraker_api_key")
        self.api_key_file = os.path.expanduser(api_key_file)
        self.api_key = self._read_api_key()
        self.auth_enabled = config.getboolean('enabled', True)
        self.trusted_connections = {}
        self.access_tokens = {}

        # Get allowed cors domains
        self.cors_domains = []
        cors_cfg = config.get('cors_domains', "").strip()
        cds = [d.strip() for d in cors_cfg.split('\n')if d.strip()]
        for domain in cds:
            bad_match = re.search(r"^.+\.[^:]*\*", domain)
            if bad_match is not None:
                raise config.error(
                    f"Unsafe CORS Domain '{domain}'.  Wildcards are not"
                    " permitted in the top level domain.")
            self.cors_domains.append(
                domain.replace(".", "\\.").replace("*", ".*"))

        # Get Trusted Clients
        self.trusted_ips = []
        self.trusted_ranges = []
        trusted_clients = config.get('trusted_clients', "")
        trusted_clients = [c.strip() for c in trusted_clients.split('\n')
                           if c.strip()]
        for ip in trusted_clients:
            # Check IP address
            try:
                tc = ipaddress.ip_address(ip)
            except ValueError:
                tc = None
            if tc is None:
                # Check ip network
                try:
                    tc = ipaddress.ip_network(ip)
                except ValueError:
                    raise ServerError(
                        f"Invalid option in trusted_clients: {ip}")
                self.trusted_ranges.append(tc)
            else:
                self.trusted_ips.append(tc)

        t_clients = "\n".join(
            [str(ip) for ip in self.trusted_ips] +
            [str(rng) for rng in self.trusted_ranges])
        c_domains = "\n".join(self.cors_domains)

        logging.info(
            f"Authorization Configuration Loaded\n"
            f"Auth Enabled: {self.auth_enabled}\n"
            f"Trusted Clients:\n{t_clients}\n"
            f"CORS Domains:\n{c_domains}")

        self.prune_handler = PeriodicCallback(
            self._prune_conn_handler, PRUNE_CHECK_TIME)
        self.prune_handler.start()
 async def _write_request(self, request: KlippyRequest) -> None:
     if self.writer is None or self.closing:
         self.pending_requests.pop(request.id, None)
         request.notify(ServerError("Klippy Host not connected", 503))
         return
     data = json.dumps(request.to_dict()).encode() + b"\x03"
     try:
         self.writer.write(data)
         await self.writer.drain()
     except asyncio.CancelledError:
         self.pending_requests.pop(request.id, None)
         request.notify(ServerError("Klippy Write Request Cancelled", 503))
         raise
     except Exception:
         self.pending_requests.pop(request.id, None)
         request.notify(ServerError("Klippy Write Request Error", 503))
         if not self.closing:
             logging.debug("Klippy Disconnection From _write_request()")
             await self.close()
Exemple #14
0
 async def wait(self):
     # Wait for klippy to process the request or until the timeout
     # has been reached.
     try:
         await self._event.wait(timeout=self._timeout)
     except TimeoutError:
         logging.info("Request '%s' Timed Out" %
                      (self.method + " " + self.path))
         return ServerError("Klippy Request Timed Out", 500)
     return self.response
Exemple #15
0
 async def readuntil(self, stop: bytes) -> bytes:
     if self.action == "wait":
         evt = asyncio.Event()
         await evt.wait()
         return b""
     elif self.action == "raise_error":
         raise ServerError("TestError")
     else:
         self.eof = True
         return b"NotJsonDecodable"
Exemple #16
0
 def raise_for_status(self, message: Optional[str] = None) -> None:
     if self._error is not None:
         code = 500
         msg = f"HTTP Request Error: {self.url}"
         if isinstance(self._error, HTTPError):
             code = self._code
             if self._error.message is not None:
                 msg = self._error.message
         if message is not None:
             msg = message
         raise ServerError(msg, code) from self._error
Exemple #17
0
 def _get_converted_arg(self, key, default=Sentinel, dtype=str):
     if key not in self.args:
         if default == Sentinel:
             raise ServerError(f"No data for argument: {key}")
         return default
     val = self.args[key]
     try:
         if dtype != bool:
             return dtype(val)
         else:
             if isinstance(val, str):
                 val = val.lower()
                 if val in ["true", "false"]:
                     return True if val == "true" else False
             elif isinstance(val, bool):
                 return val
             raise TypeError
     except Exception:
         raise ServerError(
             f"Unable to convert argument [{key}] to {dtype}: "
             f"value recieved: {val}")
Exemple #18
0
 def on_connection_closed(self):
     self.init_list = []
     self.klippy_state = "disconnected"
     for request in self.pending_requests.values():
         request.notify(ServerError("Klippy Disconnected", 503))
     self.pending_requests = {}
     logging.info("Klippy Connection Removed")
     self.send_event("server:klippy_disconnect")
     if self.init_handle is not None:
         self.ioloop.remove_timeout(self.init_handle)
     if self.server_running:
         self.ioloop.call_later(.25, self._connect_klippy)
 async def request(self, web_request: WebRequest) -> Any:
     if not self.is_connected():
         raise ServerError("Klippy Host not connected", 503)
     rpc_method = web_request.get_endpoint()
     if rpc_method == "objects/subscribe":
         return await self._request_subscripton(web_request)
     else:
         if rpc_method == "gcode/script":
             script = web_request.get_str('script', "")
             if script:
                 self.server.send_event(
                     "klippy_connection:gcode_received", script)
         return await self._request_standard(web_request)
Exemple #20
0
 def _get_converted_arg(self, key: str, default: Union[SentinelClass, _T],
                        dtype: Type[_C]) -> Union[_C, _T]:
     if key not in self.args:
         if isinstance(default, SentinelClass):
             raise ServerError(f"No data for argument: {key}")
         return default
     val = self.args[key]
     try:
         if dtype is not bool:
             return dtype(val)
         else:
             if isinstance(val, str):
                 val = val.lower()
                 if val in ["true", "false"]:
                     return True if val == "true" else False  # type: ignore
             elif isinstance(val, bool):
                 return val  # type: ignore
             raise TypeError
     except Exception:
         raise ServerError(
             f"Unable to convert argument [{key}] to {dtype}: "
             f"value recieved: {val}")
 async def _on_connection_closed(self) -> None:
     self.init_list = []
     self._state = "disconnected"
     for request in self.pending_requests.values():
         request.notify(ServerError("Klippy Disconnected", 503))
     self.pending_requests = {}
     self.subscriptions = {}
     self._peer_cred = {}
     self._missing_reqs.clear()
     logging.info("Klippy Connection Removed")
     await self.server.send_event("server:klippy_disconnect")
     if self.server.is_running():
         # Reconnect if server is running
         loop = self.event_loop
         self.connection_task = loop.create_task(self._do_connect())
Exemple #22
0
 def on_connection_closed(self) -> None:
     self.init_list = []
     self.klippy_state = "disconnected"
     for request in self.pending_requests.values():
         request.notify(ServerError("Klippy Disconnected", 503))
     self.pending_requests = {}
     self.subscriptions = {}
     logging.info("Klippy Connection Removed")
     self.send_event("server:klippy_disconnect")
     if self.init_handle is not None:
         self.init_handle.cancel()
         self.init_handle = None
     if self.server_running:
         self.event_loop.delay_callback(.25, self._connect_klippy)
     if self.klippy_disconnect_evt is not None:
         self.klippy_disconnect_evt.set()
Exemple #23
0
 def parse_args(self) -> Dict[str, Any]:
     try:
         args = self._parse_query()
     except Exception:
         raise ServerError("Error Parsing Request Arguments. "
                           "Is the Content-Type correct?")
     content_type = self.request.headers.get('Content-Type', "").strip()
     if content_type.startswith("application/json"):
         try:
             args.update(json.loads(self.request.body))
         except json.JSONDecodeError:
             pass
     for key, value in self.path_kwargs.items():
         if value is not None:
             args[key] = value
     return args
Exemple #24
0
    def make_request(self, path, method, args):
        timeout = self.long_running_requests.get(path, self.request_timeout)

        if path == "gcode/script":
            script = args.get('script', "")
            base_gc = script.strip().split()[0].upper()
            timeout = self.long_running_gcodes.get(base_gc, timeout)

        base_request = BaseRequest(path, method, args, timeout)
        self.pending_requests[base_request.id] = base_request
        ret = self.klippy_send(base_request.to_dict())
        if not ret:
            self.pending_requests.pop(base_request.id, None)
            base_request.notify(
                ServerError("Klippy Host not connected", 503))
        return base_request
Exemple #25
0
 def on_close(self) -> None:
     self.is_closed = True
     self.message_buf = []
     now = self.event_loop.get_loop_time()
     pong_elapsed = now - self.last_pong_time
     for resp in self.pending_responses.values():
         resp.set_exception(ServerError("Client Socket Disconnected", 500))
     self.pending_responses = {}
     logging.info(f"Websocket Closed: ID: {self.uid} "
                  f"Close Code: {self.close_code}, "
                  f"Close Reason: {self.close_reason}, "
                  f"Pong Time Elapsed: {pong_elapsed:.2f}")
     if self._client_data["type"] == "agent":
         extensions: ExtensionManager
         extensions = self.server.lookup_component("extensions")
         extensions.remove_agent(self)
     self.wsm.remove_websocket(self)
Exemple #26
0
    def __init__(self, config):
        api_key_file = config.get('api_key_file', "~/.moonraker_api_key")
        self.api_key_file = os.path.expanduser(api_key_file)
        self.api_key = self._read_api_key()
        self.auth_enabled = config.getboolean('enabled', True)
        self.trusted_connections = {}
        self.access_tokens = {}

        # Get Trusted Clients
        self.trusted_ips = []
        self.trusted_ranges = []
        trusted_clients = config.get('trusted_clients', "")
        trusted_clients = [
            c.strip() for c in trusted_clients.split('\n') if c.strip()
        ]
        for ip in trusted_clients:
            # Check IP address
            try:
                tc = ipaddress.ip_address(ip)
            except ValueError:
                tc = None
            if tc is None:
                # Check ip network
                try:
                    tc = ipaddress.ip_network(ip)
                except ValueError:
                    raise ServerError(
                        f"Invalid option in trusted_clients: {ip}")
                self.trusted_ranges.append(tc)
            else:
                self.trusted_ips.append(tc)

        t_clients = "\n".join([str(ip) for ip in self.trusted_ips] +
                              [str(rng) for rng in self.trusted_ranges])

        logging.info(f"Authorization Configuration Loaded\n"
                     f"Auth Enabled: {self.auth_enabled}\n"
                     f"Trusted Clients:\n{t_clients}")

        self.prune_handler = PeriodicCallback(self._prune_conn_handler,
                                              PRUNE_CHECK_TIME)
        self.prune_handler.start()
Exemple #27
0
 def load_plugin(self, plugin_name, default=Sentinel):
     if plugin_name in self.plugins:
         return self.plugins[plugin_name]
     # Make sure plugin exists
     mod_path = os.path.join(
         os.path.dirname(__file__), 'plugins', plugin_name + '.py')
     if not os.path.exists(mod_path):
         logging.info("Plugin (%s) does not exist" % (plugin_name))
         return None
     module = importlib.import_module("plugins." + plugin_name)
     try:
         load_func = getattr(module, "load_plugin")
         plugin = load_func(self)
     except Exception:
         msg = "Unable to load plugin (%s)" % (plugin_name)
         if default == Sentinel:
             raise ServerError(msg)
         return default
     self.plugins[plugin_name] = plugin
     logging.info("Plugin (%s) loaded" % (plugin_name))
     return plugin
Exemple #28
0
 def process_response(self, obj: Dict[str, Any],
                      conn: Optional[WebSocket]) -> None:
     if conn is None:
         logging.debug(f"RPC Response to non-socket request: {obj}")
         return
     response_id = obj.get("id")
     if response_id is None:
         logging.debug(f"RPC Response with null ID: {obj}")
         return
     result = obj.get("result")
     if result is None:
         name = conn.client_data["name"]
         error = obj.get("error")
         msg = f"Invalid Response: {obj}"
         code = -32600
         if isinstance(error, dict):
             msg = error.get("message", msg)
             code = error.get("code", code)
         msg = f"{name} rpc error: {code} {msg}"
         ret = ServerError(msg, 418)
     else:
         ret = result
     conn.resolve_pending_response(response_id, ret)
Exemple #29
0
 def lookup_plugin(self, plugin_name, default=Sentinel):
     plugin = self.plugins.get(plugin_name, default)
     if plugin == Sentinel:
         raise ServerError(f"Plugin ({plugin_name}) not found")
     return plugin
Exemple #30
0
 def get(self, key, default=Sentinel):
     val = self.args.get(key, default)
     if val == Sentinel:
         raise ServerError(f"No data for argument: {key}")
     return val