def __init__(self: Strip, name: str, color_order: ColorOrder, cfg: ConfigHelper): self.server = cfg.get_server() self.client = AsyncHTTPClient() self.request_mutex = asyncio.Lock() self.name = name self.color_order = color_order # Read the uri information addr: str = cfg.get("address") port: int = cfg.getint("port", 80) protocol: str = cfg.get("protocol", "http") self.url = f"{protocol}://{addr}:{port}/json" self.timeout: float = cfg.getfloat("timeout", 2.) self.initial_preset: int = cfg.getint("initial_preset", -1) self.initial_red: float = cfg.getfloat("initial_red", 0.5) self.initial_green: float = cfg.getfloat("initial_green", 0.5) self.initial_blue: float = cfg.getfloat("initial_blue", 0.5) self.initial_white: float = cfg.getfloat("initial_white", 0.5) self.chain_count: int = cfg.getint("chain_count", 1) self._chain_data = bytearray(self.chain_count * color_order.Elem_Size()) self.onoff = OnOff.off self.preset = self.initial_preset
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.temp_store_size = config.getint('temperature_store_size', 1200) self.gcode_store_size = config.getint('gcode_store_size', 1000) # Temperature Store Tracking self.last_temps: Dict[str, Tuple[float, ...]] = {} self.gcode_queue: GCQueue = deque(maxlen=self.gcode_store_size) self.temperature_store: TempStore = {} eventloop = self.server.get_event_loop() self.temp_update_timer = eventloop.register_timer( self._update_temperature_store) # Register status update event self.server.register_event_handler("server:status_update", self._set_current_temps) self.server.register_event_handler("server:gcode_response", self._update_gcode_store) self.server.register_event_handler("server:klippy_ready", self._init_sensors) self.server.register_event_handler("klippy_connection:gcode_received", self._store_gcode_command) # Register endpoints self.server.register_endpoint("/server/temperature_store", ['GET'], self._handle_temp_store_request) self.server.register_endpoint("/server/gcode_store", ['GET'], self._handle_gcode_store_request)
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.temp_store_size = config.getint('temperature_store_size', 1200) self.gcode_store_size = config.getint('gcode_store_size', 1000) # Temperature Store Tracking self.last_temps: Dict[str, Tuple[float, ...]] = {} self.gcode_queue: GCQueue = deque(maxlen=self.gcode_store_size) self.temperature_store: TempStore = {} self.temp_update_cb = PeriodicCallback( self._update_temperature_store, TEMPERATURE_UPDATE_MS) # Register status update event self.server.register_event_handler( "server:status_update", self._set_current_temps) self.server.register_event_handler( "server:gcode_response", self._update_gcode_store) self.server.register_event_handler( "server:klippy_ready", self._init_sensors) # Register endpoints self.server.register_endpoint( "/server/temperature_store", ['GET'], self._handle_temp_store_request) self.server.register_endpoint( "/server/gcode_store", ['GET'], self._handle_gcode_store_request)
def test_get_int_deprecate(self, test_config: ConfigHelper): server = test_config.get_server() test_config.getint("test_int", deprecate=True) expected = ( f"[test_options]: Option 'test_int' is " "deprecated, see the configuration documention " "at https://moonraker.readthedocs.io/en/latest/configuration") assert expected in server.warnings
def __init__(self, config: ConfigHelper) -> None: super().__init__(config) self.timer = config.get("timer", "") self.request_mutex = asyncio.Lock() addr_and_output_id = config.get("address").split('/') self.addr = addr_and_output_id[0] if (len(addr_and_output_id) > 1): self.server.add_warning( f"Power Device {self.name}: Including the output id in the" " address is deprecated, use the 'output_id' option") self.output_id: Optional[int] = int(addr_and_output_id[1]) else: self.output_id = config.getint("output_id", None) self.port = config.getint("port", 9999)
def from_config(cls, config: ConfigHelper) -> WebCam: webcam: Dict[str, Any] = {} webcam["name"] = config.get_name().split(maxsplit=1)[-1] webcam["location"] = config.get("location", "printer") webcam["service"] = config.get("service", "mjpegstreamer") webcam["target_fps"] = config.getint("target_fps", 15) webcam["stream_url"] = config.get("stream_url") webcam["snapshot_url"] = config.get("snapshot_url") webcam["flip_horizontal"] = config.getboolean("flip_horizontal", False) webcam["flip_vertical"] = config.getboolean("flip_vertical", False) webcam["rotation"] = config.getint("rotation", 0) if webcam["rotation"] not in [0, 90, 180, 270]: raise config.error("Invalid value for option 'rotation'") webcam["source"] = "config" return cls(config.get_server(), **webcam)
def test_get_int_pass_all(self, test_config: ConfigHelper): val = test_config.getint("test_int", above=0, below=2, minval=1, maxval=1) assert val == 1
def __init__(self, config: ConfigHelper) -> None: super().__init__(config) self.mqtt: MQTTClient = self.server.load_component(config, 'mqtt') self.eventloop = self.server.get_event_loop() self.cmd_topic: str = config.get('command_topic') self.cmd_payload: JinjaTemplate = config.gettemplate('command_payload') self.retain_cmd_state = config.getboolean('retain_command_state', False) self.query_topic: Optional[str] = config.get('query_topic', None) self.query_payload = config.gettemplate('query_payload', None) self.must_query = config.getboolean('query_after_command', False) if self.query_topic is not None: self.must_query = False self.state_topic: str = config.get('state_topic') self.state_timeout = config.getfloat('state_timeout', 2.) self.state_response = config.load_template('state_response_template', "{payload}") self.qos: Optional[int] = config.getint('qos', None, minval=0, maxval=2) self.mqtt.subscribe_topic(self.state_topic, self._on_state_update, self.qos) self.query_response: Optional[asyncio.Future] = None self.request_mutex = asyncio.Lock() self.server.register_event_handler("mqtt:connected", self._on_mqtt_connected) self.server.register_event_handler("mqtt:disconnected", self._on_mqtt_disconnected)
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.debug_enabled = config.getboolean('enable_repo_debug', False) if self.debug_enabled: logging.warning("UPDATE MANAGER: REPO DEBUG ENABLED") shell_cmd: SCMDComp = self.server.lookup_component('shell_command') self.scmd_error = shell_cmd.error self.build_shell_command = shell_cmd.build_shell_command self.pkg_updater: Optional[PackageDeploy] = None self.http_client = AsyncHTTPClient() self.github_request_cache: Dict[str, CachedGithubResponse] = {} # database management db: DBComp = self.server.lookup_component('database') db.register_local_namespace("update_manager") self.umdb = db.wrap_namespace("update_manager") # Refresh Time Tracking (default is to refresh every 28 days) reresh_interval = config.getint('refresh_interval', 672) # Convert to seconds self.refresh_interval = reresh_interval * 60 * 60 # GitHub API Rate Limit Tracking self.gh_rate_limit: Optional[int] = None self.gh_limit_remaining: Optional[int] = None self.gh_limit_reset_time: Optional[float] = None # Update In Progress Tracking self.cur_update_app: Optional[str] = None self.cur_update_id: Optional[int] = None self.full_complete: bool = False
def __init__(self: Strip, name: str, cfg: ConfigHelper): self.server = cfg.get_server() self.request_mutex = asyncio.Lock() self.name = name self.initial_preset: int = cfg.getint("initial_preset", -1) self.initial_red: float = cfg.getfloat("initial_red", 0.5) self.initial_green: float = cfg.getfloat("initial_green", 0.5) self.initial_blue: float = cfg.getfloat("initial_blue", 0.5) self.initial_white: float = cfg.getfloat("initial_white", 0.5) self.chain_count: int = cfg.getint("chain_count", 1) # Supports rgbw always self._chain_data = bytearray(self.chain_count * self._COLORSIZE) self.onoff = OnOff.off self.preset = self.initial_preset
def __init__(self: StripHttp, name: str, cfg: ConfigHelper): super().__init__(name, cfg) # Read the uri information addr: str = cfg.get("address") port: int = cfg.getint("port", 80) protocol: str = cfg.get("protocol", "http") self.url = f"{protocol}://{addr}:{port}/json" self.timeout: float = cfg.getfloat("timeout", 2.) self.client = AsyncHTTPClient()
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.http_server: Optional[HTTPServer] = None self.secure_server: Optional[HTTPServer] = None self.api_cache: Dict[str, APIDefinition] = {} self.registered_base_handlers: List[str] = [] self.max_upload_size = config.getint('max_upload_size', 1024) self.max_upload_size *= 1024 * 1024 # SSL config self.cert_path: str = self._get_path_option( config, 'ssl_certificate_path') self.key_path: str = self._get_path_option( config, 'ssl_key_path') # Set Up Websocket and Authorization Managers self.wsm = WebsocketManager(self.server) self.api_transports: Dict[str, APITransport] = { "websocket": self.wsm } mimetypes.add_type('text/plain', '.log') mimetypes.add_type('text/plain', '.gcode') mimetypes.add_type('text/plain', '.cfg') self.debug = config.getboolean('enable_debug_logging', False) log_level = logging.DEBUG if self.debug else logging.INFO logging.getLogger().setLevel(log_level) app_args: Dict[str, Any] = { 'serve_traceback': self.debug, 'websocket_ping_interval': 10, 'websocket_ping_timeout': 30, 'parent': self, 'default_handler_class': AuthorizedErrorHandler, 'default_handler_args': {}, 'log_function': self.log_request } # Set up HTTP only requests self.mutable_router = MutableRouter(self) app_handlers: List[Any] = [ (AnyMatches(), self.mutable_router), (r"/websocket", WebSocket), (r"/server/redirect", RedirectHandler)] self.app = tornado.web.Application(app_handlers, **app_args) self.get_handler_delegate = self.app.get_handler_delegate # Register handlers logfile = self.server.get_app_args().get('log_file') if logfile: self.register_static_file_handler( "moonraker.log", logfile, force=True) self.register_static_file_handler( "klippy.log", DEFAULT_KLIPPY_LOG_PATH, force=True)
def __init__(self, config: ConfigHelper, paneldue: PanelDue) -> None: self.event_loop = config.get_server().get_event_loop() self.paneldue = paneldue self.port: str = config.get('serial') self.baud = config.getint('baud', 57600) self.partial_input: bytes = b"" self.ser: Optional[serial.Serial] = None self.fd: Optional[int] = None self.connected: bool = False self.send_busy: bool = False self.send_buffer: bytes = b"" self.attempting_connect: bool = True
def __init__(self, config: ConfigHelper, paneldue: PanelDue) -> None: self.ioloop = IOLoop.current() self.paneldue = paneldue self.port: str = config.get('serial') self.baud = config.getint('baud', 57600) self.partial_input: bytes = b"" self.ser: Optional[serial.Serial] = None self.fd: Optional[int] = None self.connected: bool = False self.send_busy: bool = False self.send_buffer: bytes = b"" self.attempting_connect: bool = True self.ioloop.spawn_callback(self._connect)
def __init__(self, config: ConfigHelper, default_port: int = -1, default_user: str = "", default_password: str = "", default_protocol: str = "http") -> None: super().__init__(config) self.client = AsyncHTTPClient() self.request_mutex = Lock() self.addr: str = config.get("address") self.port = config.getint("port", default_port) self.user = config.get("user", default_user) self.password = config.get("password", default_password) self.protocol = config.get("protocol", default_protocol)
def __init__(self, config: ConfigHelper, cmd_helper: CommandHelper, name: Optional[str] = None, prefix: str = "", cfg_hash: Optional[str] = None) -> None: if name is None: name = config.get_name().split()[-1] self.name = name if prefix: prefix = f"{prefix} {self.name}: " self.prefix = prefix self.server = config.get_server() self.cmd_helper = cmd_helper self.refresh_interval = cmd_helper.get_refresh_interval() refresh_interval = config.getint('refresh_interval', None) if refresh_interval is not None: self.refresh_interval = refresh_interval * 60 * 60 if cfg_hash is None: cfg_hash = config.get_hash().hexdigest() self.cfg_hash = cfg_hash
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.ldap_host = config.get('ldap_host') self.ldap_port = config.getint("ldap_port", None) self.ldap_secure = config.getboolean("ldap_secure", False) base_dn_template = config.gettemplate('base_dn') self.base_dn = base_dn_template.render() self.group_dn: Optional[str] = None group_dn_template = config.gettemplate("group_dn", None) if group_dn_template is not None: self.group_dn = group_dn_template.render() self.active_directory = config.getboolean('is_active_directory', False) self.bind_dn: Optional[str] = None self.bind_password: Optional[str] = None bind_dn_template = config.gettemplate('bind_dn', None) bind_pass_template = config.gettemplate('bind_password', None) if bind_dn_template is not None: self.bind_dn = bind_dn_template.render() if bind_pass_template is None: raise config.error("Section [ldap]: Option 'bind_password' is " "required when 'bind_dn' is provided") self.bind_password = bind_pass_template.render() self.lock = asyncio.Lock()
def __init__(self, config: ConfigHelper) -> None: super().__init__(config, default_user="******", default_password="") self.output_id = config.getint("output_id", 0) self.timer = config.get("timer", "")
def __init__(self, config: ConfigHelper) -> None: super().__init__(config, default_user="******", default_password="") self.device = config.getint("device")
def __init__(self: StripSerial, name: str, cfg: ConfigHelper): super().__init__(name, cfg) # Read the serial information (requires wled 0.13 2108250 or greater) self.serialport: str = cfg.get("serial") self.baud: int = cfg.getint("baud", 115200, above=49)
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.login_timeout = config.getint('login_timeout', 90) self.force_logins = config.getboolean('force_logins', False) database: DBComp = self.server.lookup_component('database') database.register_local_namespace('authorized_users', forbidden=True) self.users = database.wrap_namespace('authorized_users') api_user: Optional[Dict[str, Any]] = self.users.get(API_USER, None) if api_user is None: self.api_key = uuid.uuid4().hex self.users[API_USER] = { 'username': API_USER, 'api_key': self.api_key, 'created_on': time.time() } else: self.api_key = api_user['api_key'] host_name, port = self.server.get_host_info() self.issuer = f"http://{host_name}:{port}" self.public_jwks: Dict[str, Dict[str, Any]] = {} for username, user_info in list(self.users.items()): if username == API_USER: continue if 'jwt_secret' in user_info: try: priv_key = self._load_private_key(user_info['jwt_secret']) jwk_id = user_info['jwk_id'] except (self.server.error, KeyError): logging.info("Invalid key found for user, removing") user_info.pop('jwt_secret', None) user_info.pop('jwk_id', None) self.users[username] = user_info continue self.public_jwks[jwk_id] = self._generate_public_jwk(priv_key) self.trusted_users: Dict[IPAddr, Any] = {} self.oneshot_tokens: Dict[str, OneshotToken] = {} self.permitted_paths: Set[str] = set() # Get allowed cors domains self.cors_domains: List[str] = [] 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: List[IPAddr] = [] self.trusted_ranges: List[IPNetwork] = [] self.trusted_domains: List[str] = [] tcs = config.get('trusted_clients', "") trusted_clients = [c.strip() for c in tcs.split('\n') if c.strip()] for val in trusted_clients: # Check IP address try: tc = ipaddress.ip_address(val) except ValueError: pass else: self.trusted_ips.append(tc) continue # Check ip network try: tc = ipaddress.ip_network(val) except ValueError: pass else: self.trusted_ranges.append(tc) continue # Check hostname self.trusted_domains.append(val.lower()) t_clients = "\n".join([str(ip) for ip in self.trusted_ips] + [str(rng) for rng in self.trusted_ranges] + self.trusted_domains) c_domains = "\n".join(self.cors_domains) logging.info(f"Authorization Configuration Loaded\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() # Register Authorization Endpoints self.permitted_paths.add("/server/redirect") self.permitted_paths.add("/access/login") self.permitted_paths.add("/access/refresh_jwt") self.server.register_endpoint("/access/login", ['POST'], self._handle_login, protocol=['http']) self.server.register_endpoint("/access/logout", ['POST'], self._handle_logout, protocol=['http']) self.server.register_endpoint("/access/refresh_jwt", ['POST'], self._handle_refresh_jwt, protocol=['http']) self.server.register_endpoint("/access/user", ['GET', 'POST', 'DELETE'], self._handle_user_request, protocol=['http']) self.server.register_endpoint("/access/users/list", ['GET'], self._handle_list_request, protocol=['http']) self.server.register_endpoint("/access/user/password", ['POST'], self._handle_password_reset, protocol=['http']) self.server.register_endpoint("/access/api_key", ['GET', 'POST'], self._handle_apikey_request, protocol=['http']) self.server.register_endpoint("/access/oneshot_token", ['GET'], self._handle_oneshot_request, protocol=['http']) self.server.register_notification("authorization:user_created") self.server.register_notification("authorization:user_deleted")
def test_get_int_fail_below(self, test_config: ConfigHelper): with pytest.raises(ConfigError): test_config.getint("test_int", below=1)
def test_get_int_fail_above(self, test_config: ConfigHelper): with pytest.raises(ConfigError): test_config.getint("test_int", above=1)
def test_get_int_default(self, test_config: ConfigHelper): assert test_config.getint("invalid_option", None) is None
def test_get_int_fail(self, test_config: ConfigHelper): with pytest.raises(ConfigError): test_config.getint("invalid_option")
def test_get_int_exists(self, test_config: ConfigHelper): val = test_config.getint("test_int") assert val == 1
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.address: str = config.get('address') self.port: int = config.getint('port', 1883) self.user_name = config.get('username', None) pw_file_path = config.get('password_file', None) self.password: Optional[str] = None if pw_file_path is not None: pw_file = pathlib.Path(pw_file_path).expanduser().absolute() if not pw_file.exists(): raise config.error(f"Password file '{pw_file}' does not exist") self.password = pw_file.read_text().strip() protocol = config.get('mqtt_protocol', "v3.1.1") self.protocol = MQTT_PROTOCOLS.get(protocol, None) if self.protocol is None: raise config.error( f"Invalid value '{protocol}' for option 'mqtt_protocol' " "in section [mqtt]. Must be one of " f"{MQTT_PROTOCOLS.values()}") self.instance_name = config.get('instance_name', socket.gethostname()) if '+' in self.instance_name or '#' in self.instance_name: raise config.error( "Option 'instance_name' in section [mqtt] cannot " "contain a wildcard.") self.qos = config.getint("default_qos", 0) if self.qos > 2 or self.qos < 0: raise config.error( "Option 'default_qos' in section [mqtt] must be " "between 0 and 2") self.client = paho_mqtt.Client(protocol=self.protocol) self.client.on_connect = self._on_connect self.client.on_message = self._on_message self.client.on_disconnect = self._on_disconnect self.client.on_publish = self._on_publish self.client.on_subscribe = self._on_subscribe self.client.on_unsubscribe = self._on_unsubscribe self.connect_evt: asyncio.Event = asyncio.Event() self.disconnect_evt: Optional[asyncio.Event] = None self.reconnect_task: Optional[asyncio.Task] = None self.subscribed_topics: SubscribedDict = {} self.pending_responses: List[asyncio.Future] = [] self.pending_acks: Dict[int, asyncio.Future] = {} self.server.register_endpoint("/server/mqtt/publish", ["POST"], self._handle_publish_request, transports=["http", "websocket"]) self.server.register_endpoint("/server/mqtt/subscribe", ["POST"], self._handle_subscription_request, transports=["http", "websocket"]) # Subscribe to API requests self.json_rpc = JsonRPC(transport="MQTT") self.api_request_topic = f"{self.instance_name}/moonraker/api/request" self.api_resp_topic = f"{self.instance_name}/moonraker/api/response" self.timestamp_deque: Deque = deque(maxlen=20) self.api_qos = config.getint('api_qos', self.qos) if config.getboolean("enable_moonraker_api", True): api_cache = self.server.register_api_transport("mqtt", self) for api_def in api_cache.values(): if "mqtt" in api_def.supported_transports: self.register_api_handler(api_def) self.subscribe_topic(self.api_request_topic, self._process_api_request, self.api_qos) logging.info( f"Moonraker API topics - Request: {self.api_request_topic}, " f"Response: {self.api_resp_topic}") IOLoop.current().spawn_callback(self._initialize)
def test_get_int_fail_maxval(self, test_config: ConfigHelper): with pytest.raises(ConfigError): test_config.getint("test_int", maxval=0)
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.login_timeout = config.getint('login_timeout', 90) self.force_logins = config.getboolean('force_logins', False) database: DBComp = self.server.lookup_component('database') database.register_local_namespace('authorized_users', forbidden=True) self.users = database.wrap_namespace('authorized_users') api_user: Optional[Dict[str, Any]] = self.users.get(API_USER, None) if api_user is None: self.api_key = uuid.uuid4().hex self.users[API_USER] = { 'username': API_USER, 'api_key': self.api_key, 'created_on': time.time() } else: self.api_key = api_user['api_key'] hi = self.server.get_host_info() self.issuer = f"http://{hi['hostname']}:{hi['port']}" self.public_jwks: Dict[str, Dict[str, Any]] = {} for username, user_info in list(self.users.items()): if username == API_USER: # Validate the API User for item in ["username", "api_key", "created_on"]: if item not in user_info: self.users[API_USER] = { 'username': API_USER, 'api_key': self.api_key, 'created_on': time.time() } break continue else: # validate created users valid = True for item in ["username", "password", "salt", "created_on"]: if item not in user_info: logging.info( f"Authorization: User {username} does not " f"contain field {item}, removing") del self.users[username] valid = False break if not valid: continue # generate jwks for valid users if 'jwt_secret' in user_info: try: priv_key = self._load_private_key(user_info['jwt_secret']) jwk_id = user_info['jwk_id'] except (self.server.error, KeyError): logging.info("Invalid key found for user, removing") user_info.pop('jwt_secret', None) user_info.pop('jwk_id', None) self.users[username] = user_info continue self.public_jwks[jwk_id] = self._generate_public_jwk(priv_key) self.trusted_users: Dict[IPAddr, Any] = {} self.oneshot_tokens: Dict[str, OneshotToken] = {} self.permitted_paths: Set[str] = set() # Get allowed cors domains self.cors_domains: List[str] = [] for domain in config.getlist('cors_domains', []): 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.") if domain.endswith("/"): self.server.add_warning( f"[authorization]: Invalid domain '{domain}' in option " "'cors_domains'. Domain's cannot contain a trailing " "slash.") else: self.cors_domains.append( domain.replace(".", "\\.").replace("*", ".*")) # Get Trusted Clients self.trusted_ips: List[IPAddr] = [] self.trusted_ranges: List[IPNetwork] = [] self.trusted_domains: List[str] = [] for val in config.getlist('trusted_clients', []): # Check IP address try: tc = ipaddress.ip_address(val) except ValueError: pass else: self.trusted_ips.append(tc) continue # Check ip network try: tc = ipaddress.ip_network(val) except ValueError as e: if "has host bits set" in str(e): self.server.add_warning( f"[authorization]: Invalid CIDR expression '{val}' " "in option 'trusted_clients'") continue pass else: self.trusted_ranges.append(tc) continue # Check hostname match = re.match(r"([a-z0-9]+(-[a-z0-9]+)*\.?)+[a-z]{2,}$", val) if match is not None: self.trusted_domains.append(val.lower()) else: self.server.add_warning( f"[authorization]: Invalid domain name '{val}' " "in option 'trusted_clients'") t_clients = "\n".join([str(ip) for ip in self.trusted_ips] + [str(rng) for rng in self.trusted_ranges] + self.trusted_domains) c_domains = "\n".join(self.cors_domains) logging.info(f"Authorization Configuration Loaded\n" f"Trusted Clients:\n{t_clients}\n" f"CORS Domains:\n{c_domains}") eventloop = self.server.get_event_loop() self.prune_timer = eventloop.register_timer(self._prune_conn_handler) # Register Authorization Endpoints self.permitted_paths.add("/server/redirect") self.permitted_paths.add("/access/login") self.permitted_paths.add("/access/refresh_jwt") self.server.register_endpoint("/access/login", ['POST'], self._handle_login, transports=['http']) self.server.register_endpoint("/access/logout", ['POST'], self._handle_logout, transports=['http']) self.server.register_endpoint("/access/refresh_jwt", ['POST'], self._handle_refresh_jwt, transports=['http']) self.server.register_endpoint("/access/user", ['GET', 'POST', 'DELETE'], self._handle_user_request, transports=['http']) self.server.register_endpoint("/access/users/list", ['GET'], self._handle_list_request, transports=['http']) self.server.register_endpoint("/access/user/password", ['POST'], self._handle_password_reset, transports=['http']) self.server.register_endpoint("/access/api_key", ['GET', 'POST'], self._handle_apikey_request, transports=['http']) self.server.register_endpoint("/access/oneshot_token", ['GET'], self._handle_oneshot_request, transports=['http']) self.server.register_notification("authorization:user_created") self.server.register_notification("authorization:user_deleted")
def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.event_loop = self.server.get_event_loop() self.address: str = config.get('address') self.port: int = config.getint('port', 1883) user = config.gettemplate('username', None) self.user_name: Optional[str] = None if user: self.user_name = user.render() pw_file_path = config.get('password_file', None, deprecate=True) pw_template = config.gettemplate('password', None) self.password: Optional[str] = None if pw_file_path is not None: pw_file = pathlib.Path(pw_file_path).expanduser().absolute() if not pw_file.exists(): raise config.error( f"Password file '{pw_file}' does not exist") self.password = pw_file.read_text().strip() if pw_template is not None: self.password = pw_template.render() protocol = config.get('mqtt_protocol', "v3.1.1") self.protocol = MQTT_PROTOCOLS.get(protocol, None) if self.protocol is None: raise config.error( f"Invalid value '{protocol}' for option 'mqtt_protocol' " "in section [mqtt]. Must be one of " f"{MQTT_PROTOCOLS.values()}") self.instance_name = config.get('instance_name', socket.gethostname()) if '+' in self.instance_name or '#' in self.instance_name: raise config.error( "Option 'instance_name' in section [mqtt] cannot " "contain a wildcard.") self.qos = config.getint("default_qos", 0) if self.qos > 2 or self.qos < 0: raise config.error( "Option 'default_qos' in section [mqtt] must be " "between 0 and 2") self.client = paho_mqtt.Client(protocol=self.protocol) self.client.on_connect = self._on_connect self.client.on_message = self._on_message self.client.on_disconnect = self._on_disconnect self.client.on_publish = self._on_publish self.client.on_subscribe = self._on_subscribe self.client.on_unsubscribe = self._on_unsubscribe self.connect_evt: asyncio.Event = asyncio.Event() self.disconnect_evt: Optional[asyncio.Event] = None self.reconnect_task: Optional[asyncio.Task] = None self.subscribed_topics: SubscribedDict = {} self.pending_responses: List[asyncio.Future] = [] self.pending_acks: Dict[int, asyncio.Future] = {} self.server.register_endpoint( "/server/mqtt/publish", ["POST"], self._handle_publish_request, transports=["http", "websocket", "internal"]) self.server.register_endpoint( "/server/mqtt/subscribe", ["POST"], self._handle_subscription_request, transports=["http", "websocket", "internal"]) # Subscribe to API requests self.json_rpc = JsonRPC(transport="MQTT") self.api_request_topic = f"{self.instance_name}/moonraker/api/request" self.api_resp_topic = f"{self.instance_name}/moonraker/api/response" self.klipper_status_topic = f"{self.instance_name}/klipper/status" self.moonraker_status_topic = f"{self.instance_name}/moonraker/status" status_cfg: Dict[str, Any] = config.getdict("status_objects", {}, allow_empty_fields=True) self.status_objs: Dict[str, Any] = {} for key, val in status_cfg.items(): if val is not None: self.status_objs[key] = [v.strip() for v in val.split(',') if v.strip()] else: self.status_objs[key] = None if status_cfg: logging.debug(f"MQTT: Status Objects Set: {self.status_objs}") self.server.register_event_handler("server:klippy_identified", self._handle_klippy_identified) self.timestamp_deque: Deque = deque(maxlen=20) self.api_qos = config.getint('api_qos', self.qos) if config.getboolean("enable_moonraker_api", True): api_cache = self.server.register_api_transport("mqtt", self) for api_def in api_cache.values(): if "mqtt" in api_def.supported_transports: self.register_api_handler(api_def) self.subscribe_topic(self.api_request_topic, self._process_api_request, self.api_qos) self.server.register_remote_method("publish_mqtt_topic", self._publish_from_klipper) logging.info( f"\nReserved MQTT topics:\n" f"API Request: {self.api_request_topic}\n" f"API Response: {self.api_resp_topic}\n" f"Moonraker Status: {self.moonraker_status_topic}\n" f"Klipper Status: {self.klipper_status_topic}")