示例#1
0
    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
示例#2
0
    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)
示例#3
0
    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)
示例#4
0
 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
示例#5
0
 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)
示例#6
0
 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)
示例#7
0
 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
示例#8
0
    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)
示例#9
0
    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
示例#10
0
文件: wled.py 项目: ihrapsa/moonraker
    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
示例#11
0
文件: wled.py 项目: ihrapsa/moonraker
    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()
示例#12
0
    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)
示例#13
0
 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
示例#14
0
 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)
示例#15
0
 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)
示例#16
0
 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
示例#17
0
文件: ldap.py 项目: Arksine/moonraker
 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()
示例#18
0
 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", "")
示例#19
0
 def __init__(self, config: ConfigHelper) -> None:
     super().__init__(config, default_user="******", default_password="")
     self.device = config.getint("device")
示例#20
0
文件: wled.py 项目: ihrapsa/moonraker
    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)
示例#21
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']
        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")
示例#22
0
 def test_get_int_fail_below(self, test_config: ConfigHelper):
     with pytest.raises(ConfigError):
         test_config.getint("test_int", below=1)
示例#23
0
 def test_get_int_fail_above(self, test_config: ConfigHelper):
     with pytest.raises(ConfigError):
         test_config.getint("test_int", above=1)
示例#24
0
 def test_get_int_default(self, test_config: ConfigHelper):
     assert test_config.getint("invalid_option", None) is None
示例#25
0
 def test_get_int_fail(self, test_config: ConfigHelper):
     with pytest.raises(ConfigError):
         test_config.getint("invalid_option")
示例#26
0
 def test_get_int_exists(self, test_config: ConfigHelper):
     val = test_config.getint("test_int")
     assert val == 1
示例#27
0
    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)
示例#28
0
 def test_get_int_fail_maxval(self, test_config: ConfigHelper):
     with pytest.raises(ConfigError):
         test_config.getint("test_int", maxval=0)
示例#29
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")
示例#30
0
    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}")