From e64b1ba67c6861137de983399b6d799692ece67a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 9 Aug 2022 11:30:49 +0530 Subject: [PATCH] Start work on remote control passwords --- kitty/conf/utils.py | 27 +++++++++++++++------------ kitty/options/definition.py | 26 ++++++++++++++++++++++++++ kitty/options/parse.py | 15 ++++++++++----- kitty/options/types.py | 3 +++ kitty/options/utils.py | 10 ++++++++++ kitty/remote_control.py | 2 +- 6 files changed, 65 insertions(+), 18 deletions(-) diff --git a/kitty/conf/utils.py b/kitty/conf/utils.py index afe658bb9..553892aaa 100644 --- a/kitty/conf/utils.py +++ b/kitty/conf/utils.py @@ -79,24 +79,27 @@ class ToCmdline: self.override_env.update(override) return self - def __call__(self, x: str) -> List[str]: - return list( - map( - lambda y: expandvars( - os.path.expanduser(y), - os.environ if self.override_env is None else self.override_env, - fallback_to_os_env=False - ), - shlex.split(x) + def __call__(self, x: str, expand: bool = True) -> List[str]: + ans = shlex.split(x) + if expand: + ans = list( + map( + lambda y: expandvars( + os.path.expanduser(y), + os.environ if self.override_env is None else self.override_env, + fallback_to_os_env=False + ), + ans + ) ) - ) + return ans to_cmdline_implementation = ToCmdline() -def to_cmdline(x: str) -> List[str]: - return to_cmdline_implementation(x) +def to_cmdline(x: str, expand: bool = True) -> List[str]: + return to_cmdline_implementation(x, expand) def python_string(text: str) -> str: diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 28a768e0a..af18cff4f 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -2687,6 +2687,32 @@ their stdout/stderr/stdin no longer work. ''' ) +opt('+remote_control_password', '', option_type='remote_control_password', add_to_default=False, + long_text=''' +Allow other programs to control kitty using passwords. This option can be specified multiple +times to add multiple passwords. If no passwords are present kitty will ask the user for +permission if a program tries to use remote control with a password. A password can also +*optionally* be associated with a set of allowed remote control actions. For example:: + + remote_control_password "my passphrase" get-colors set-colors focus-window focus-tab + +Only the specified actions will be allowed when using this password. +Glob patterns can be used too, for example:: + + remote_control_password "my passphrase" set-tab-* resize-* + +To get a list of available actions, run:: + + kitty @ -h + +Finally, the path to a python module can be specified that provides a function :code:`is_cmd_allowed` +that is used to check every remote control command. See :ref:`rc_custom_auth` for details. For example:: + + remote_control_password "my passphrase" my_rc_command_checker.py + +Relative paths are resolved from the kitty configuration directory. +''') + opt('allow_remote_control', 'no', option_type='allow_remote_control', long_text=''' diff --git a/kitty/options/parse.py b/kitty/options/parse.py index a46de39fe..24b0690e3 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -13,11 +13,11 @@ from kitty.options.utils import ( deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations, macos_option_as_alt, macos_titlebar_color, modify_font, narrow_symbols, optional_edge_width, parse_map, parse_mouse_map, paste_actions, - resize_draw_strategy, scrollback_lines, scrollback_pager_history_size, shell_integration, - store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge, tab_bar_margin_height, - tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, tab_title_template, titlebar_color, - to_cursor_shape, to_font_size, to_layout_names, to_modifiers, url_prefixes, url_style, - visual_window_select_characters, window_border_width, window_size + remote_control_password, resize_draw_strategy, scrollback_lines, scrollback_pager_history_size, + shell_integration, store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge, + tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, + tab_title_template, titlebar_color, to_cursor_shape, to_font_size, to_layout_names, to_modifiers, + url_prefixes, url_style, visual_window_select_characters, window_border_width, window_size ) @@ -1131,6 +1131,10 @@ class Parser: def remember_window_size(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['remember_window_size'] = to_bool(val) + def remote_control_password(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + for k, v in remote_control_password(val, ans["remote_control_password"]): + ans["remote_control_password"][k] = v + def repaint_delay(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['repaint_delay'] = positive_int(val) @@ -1370,6 +1374,7 @@ def create_result_dict() -> typing.Dict[str, typing.Any]: 'kitten_alias': {}, 'modify_font': {}, 'narrow_symbols': {}, + 'remote_control_password': {}, 'symbol_map': {}, 'watcher': {}, 'map': [], diff --git a/kitty/options/types.py b/kitty/options/types.py index 639e137e5..45a5aa00a 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -400,6 +400,7 @@ option_names = ( # {{{ 'pointer_shape_when_dragging', 'pointer_shape_when_grabbed', 'remember_window_size', + 'remote_control_password', 'repaint_delay', 'resize_debounce_time', 'resize_draw_strategy', @@ -609,6 +610,7 @@ class Options: kitten_alias: typing.Dict[str, str] = {} modify_font: typing.Dict[str, kitty.fonts.FontModification] = {} narrow_symbols: typing.Dict[typing.Tuple[int, int], int] = {} + remote_control_password: typing.Dict[str, typing.Tuple[str, ...]] = {} symbol_map: typing.Dict[typing.Tuple[int, int], str] = {} watcher: typing.Dict[str, str] = {} map: typing.List[kitty.options.utils.KeyDefinition] = [] @@ -729,6 +731,7 @@ defaults.font_features = {} defaults.kitten_alias = {} defaults.modify_font = {} defaults.narrow_symbols = {} +defaults.remote_control_password = {} defaults.symbol_map = {} defaults.watcher = {} defaults.map = [ diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 9f205fad6..871286fc7 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -675,6 +675,16 @@ def config_or_absolute_path(x: str, env: Optional[Dict[str, str]] = None) -> Opt return resolve_abs_or_config_path(x, env) +def remote_control_password(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, Tuple[str, ...]]]: + val = val.strip() + if val: + parts = to_cmdline(val, expand=False) + if len(parts) == 1: + yield parts[0], () + else: + yield parts[0], tuple(parts[1:]) + + def allow_remote_control(x: str) -> str: if x != 'socket-only': x = 'y' if to_bool(x) else 'n' diff --git a/kitty/remote_control.py b/kitty/remote_control.py index de4e8d515..e7a33a751 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -170,7 +170,7 @@ def do_io(to: Optional[str], send: Dict[str, Any], no_response: bool, response_t cli_msg = ( 'Control {appname} by sending it commands. Set the' - ' :opt:`allow_remote_control` option to yes in :file:`kitty.conf` for this' + ' :opt:`allow_remote_control` option in :file:`kitty.conf` or use a password, for this' ' to work.' ).format(appname=appname)