diff options
author | Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> | 2023-08-12 22:35:42 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-12 15:35:42 +0200 |
commit | c8ddb23297e2f4fc900b8286d57e2808ae6a4fdb (patch) | |
tree | c96d26c8bfc24172374aefe88624b78c6890663f /zellij-utils | |
parent | a1903b6b048f8257fc16ffd09e19c825248cb9d6 (diff) |
feat: add plugin permission system (#2624)
* WIP: add exaple of permission ui
* feat: add request permission ui
* feat: add caching permission in memory
* feat: add permission check
* feat: add file caching
* fix: changes request
* feat(ui): new status bar mode (#2619)
* supermode prototype
* fix integration tests
* fix tests
* style(fmt): rustfmt
* docs(changelog): status-bar supermode
* fix(rendering): occasional glitches while resizing (#2621)
* docs(changelog): resize glitches fix
* chore(version): bump development version
* Fix colored pane frames in mirrored sessions (#2625)
* server/panes/tiled: Fix colored frames
in mirrored sessions. Colored frames were previously ignored because
they were treated like floating panes when rendering tiled panes.
* CHANGELOG: Add PR #2625
* server/tab/unit: Fix unit tests for server.
* fix(sessions): use custom lists of adjectives and nouns for generating session names (#2122)
* Create custom lists of adjectives and nouns for generating session names
* move word lists to const slices
* add logic to retry name generation
* refactor
- reuse the name generator
- iterator instead of for loop
---------
Co-authored-by: Thomas Linford <linford.t@gmail.com>
* docs(changelog): generate session names with custom words list
* feat(plugins): make plugins configurable (#2646)
* work
* make every plugin entry point configurable
* make integration tests pass
* make e2e tests pass
* add test for plugin configuration
* add test snapshot
* add plugin config parsing test
* cleanups
* style(fmt): rustfmt
* style(comment): remove commented code
* docs(changelog): configurable plugins
* style(fmt): rustfmt
* touch up ui
* fix: don't save permission data in memory
* feat: load cached permission
* test: add example test (WIP)
* fix: issue event are always denied
* test: update snapshot
* apply formatting
* refactor: update default cache function
* test: add more new test
* apply formatting
* Revert "apply formatting"
This reverts commit a4e93703fbfdb6865131daa1c8b90fc5c36ab25e.
* apply format
* fix: update cache path
* apply format
* fix: cache path
* fix: update log level
* test for github workflow
* Revert "test for github workflow"
This reverts commit 01eff3bc5d1627a4e60bc6dac8ebe5500bc5b56e.
* refactor: permission cache
* fix(test): permission grant/deny race condition
* style(fmt): rustfmt
* style(fmt): rustfmt
* configure permissions
* permission denied test
* snapshot
* add ui for small plugins
* style(fmt): rustfmt
* some cleanups
---------
Co-authored-by: Aram Drevekenin <aram@poor.dev>
Co-authored-by: har7an <99636919+har7an@users.noreply.github.com>
Co-authored-by: Kyle Sutherland-Cash <kyle.sutherlandcash@gmail.com>
Co-authored-by: Thomas Linford <linford.t@gmail.com>
Co-authored-by: Thomas Linford <tlinford@users.noreply.github.com>
Diffstat (limited to 'zellij-utils')
18 files changed, 311 insertions, 10 deletions
diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm Binary files differindex f38c500fc..9e2112cf9 100755 --- a/zellij-utils/assets/plugins/compact-bar.wasm +++ b/zellij-utils/assets/plugins/compact-bar.wasm diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm Binary files differindex 80a6776e9..ff13cefd0 100755 --- a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm +++ b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm Binary files differindex 58beaacc8..e18d4edf9 100755 --- a/zellij-utils/assets/plugins/status-bar.wasm +++ b/zellij-utils/assets/plugins/status-bar.wasm diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm Binary files differindex db61209a8..3aff9cc30 100755 --- a/zellij-utils/assets/plugins/strider.wasm +++ b/zellij-utils/assets/plugins/strider.wasm diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm Binary files differindex 1a2919f6f..ee53ba180 100755 --- a/zellij-utils/assets/plugins/tab-bar.wasm +++ b/zellij-utils/assets/plugins/tab-bar.wasm diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index 50600a934..a2c3a42a5 100644 --- a/zellij-utils/src/consts.rs +++ b/zellij-utils/src/consts.rs @@ -36,6 +36,8 @@ lazy_static! { .cache_dir() .to_path_buf() .join(format!("{}", Uuid::new_v4())); + pub static ref ZELLIJ_PLUGIN_PERMISSIONS_CACHE: PathBuf = + ZELLIJ_CACHE_DIR.join("permissions.kdl"); } pub const FEATURES: &[&str] = &[ diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 9c1c8e2f6..a08e642f9 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -6,7 +6,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use std::path::{Path, PathBuf}; use std::str::FromStr; -use strum_macros::{EnumDiscriminants, EnumIter, EnumString, ToString}; +use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString, ToString}; pub type ClientId = u16; // TODO: merge with crate type? @@ -493,6 +493,63 @@ pub enum Event { FileSystemUpdate(Vec<PathBuf>), /// A file was deleted somewhere in the Zellij CWD folder FileSystemDelete(Vec<PathBuf>), + /// A Result of plugin permission request + PermissionRequestResult(PermissionStatus), +} + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Copy, + Clone, + EnumDiscriminants, + ToString, + Serialize, + Deserialize, + PartialOrd, + Ord, +)] +#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize, Display, PartialOrd, Ord))] +#[strum_discriminants(name(PermissionType))] +#[non_exhaustive] +pub enum Permission { + ReadApplicationState, + ChangeApplicationState, + OpenFiles, + RunCommands, + OpenTerminalsOrPlugins, + WriteToStdin, +} + +impl PermissionType { + pub fn display_name(&self) -> String { + match self { + PermissionType::ReadApplicationState => { + "Access Zellij state (Panes, Tabs and UI)".to_owned() + }, + PermissionType::ChangeApplicationState => { + "Change Zellij state (Panes, Tabs and UI)".to_owned() + }, + PermissionType::OpenFiles => "Open files (eg. for editing)".to_owned(), + PermissionType::RunCommands => "Run commands".to_owned(), + PermissionType::OpenTerminalsOrPlugins => "Start new terminals and plugins".to_owned(), + PermissionType::WriteToStdin => "Write to standard input (STDIN)".to_owned(), + } + } +} + +#[derive(Debug, Clone)] +pub struct PluginPermission { + pub name: String, + pub permissions: Vec<PermissionType>, +} + +impl PluginPermission { + pub fn new(name: String, permissions: Vec<PermissionType>) -> Self { + PluginPermission { name, permissions } + } } /// Describes the different input modes, which change the way that keystrokes will be interpreted. @@ -811,6 +868,12 @@ pub enum CopyDestination { System, } +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +pub enum PermissionStatus { + Granted, + Denied, +} + #[derive(Debug, Default, Clone)] pub struct FileToOpen { pub path: PathBuf, @@ -882,7 +945,9 @@ impl PluginMessage { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, EnumDiscriminants, ToString)] +#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))] +#[strum_discriminants(name(CommandType))] pub enum PluginCommand { Subscribe(HashSet<EventType>), Unsubscribe(HashSet<EventType>), @@ -950,4 +1015,5 @@ pub enum PluginCommand { RenamePluginPane(u32, String), // plugin pane id, new name RenameTab(u32, String), // tab index, new name ReportPanic(String), // stringified panic + RequestPluginPermissions(Vec<PermissionType>), } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index df5d02782..306db448c 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -338,6 +338,7 @@ pub enum ScreenContext { FocusPaneWithId, RenamePane, RenameTab, + RequestPluginPermissions, BreakPane, BreakPaneRight, BreakPaneLeft, @@ -377,6 +378,7 @@ pub enum PluginContext { PostMessageToPluginWorker, PostMessageToPlugin, PluginSubscribedToEvents, + PermissionRequestResult, } /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 6722cfd88..1c95f348b 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -4,6 +4,7 @@ pub mod config; pub mod keybinds; pub mod layout; pub mod options; +pub mod permission; pub mod plugins; pub mod theme; diff --git a/zellij-utils/src/input/permission.rs b/zellij-utils/src/input/permission.rs new file mode 100644 index 000000000..1755fe1e6 --- /dev/null +++ b/zellij-utils/src/input/permission.rs @@ -0,0 +1,60 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + io::Write, + path::PathBuf, +}; + +use crate::{consts::ZELLIJ_PLUGIN_PERMISSIONS_CACHE, data::PermissionType}; + +pub type GrantedPermission = HashMap<String, Vec<PermissionType>>; + +#[derive(Default, Debug)] +pub struct PermissionCache { + path: PathBuf, + granted: GrantedPermission, +} + +impl PermissionCache { + pub fn cache(&mut self, plugin_name: String, permissions: Vec<PermissionType>) { + self.granted.insert(plugin_name, permissions); + } + + pub fn get_permissions(&self, plugin_name: String) -> Option<&Vec<PermissionType>> { + self.granted.get(&plugin_name) + } + + pub fn check_permissions( + &self, + plugin_name: String, + permissions: &Vec<PermissionType>, + ) -> bool { + if let Some(target) = self.granted.get(&plugin_name) { + if target == permissions { + return true; + } + } + + false + } + + pub fn from_path_or_default(cache_path: Option<PathBuf>) -> Self { + let cache_path = cache_path.unwrap_or(ZELLIJ_PLUGIN_PERMISSIONS_CACHE.to_path_buf()); + + let granted = match fs::read_to_string(cache_path.clone()) { + Ok(raw_string) => PermissionCache::from_string(raw_string).unwrap_or_default(), + Err(_) => GrantedPermission::default(), + }; + + PermissionCache { + path: cache_path, + granted, + } + } + + pub fn write_to_file(&self) -> std::io::Result<()> { + let mut f = File::create(&self.path)?; + write!(f, "{}", PermissionCache::to_string(&self.granted))?; + Ok(()) + } +} diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index a75a4a638..13a03ad15 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -1,15 +1,16 @@ mod kdl_layout_parser; -use crate::data::{Direction, InputMode, Key, Palette, PaletteColor, Resize}; +use crate::data::{Direction, InputMode, Key, Palette, PaletteColor, PermissionType, Resize}; use crate::envs::EnvironmentVariables; use crate::input::config::{Config, ConfigError, KdlError}; use crate::input::keybinds::Keybinds; use crate::input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}; use crate::input::options::{Clipboard, OnForceClose, Options}; +use crate::input::permission::{GrantedPermission, PermissionCache}; use crate::input::plugins::{PluginConfig, PluginTag, PluginType, PluginsConfig}; use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig}; use crate::setup::{find_default_config_dir, get_layout_dir}; use kdl_layout_parser::KdlLayoutParser; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use strum::IntoEnumIterator; use miette::NamedSource; @@ -1793,6 +1794,53 @@ impl Themes { } } +impl PermissionCache { + pub fn from_string(raw_string: String) -> Result<GrantedPermission, ConfigError> { + let kdl_document: KdlDocument = raw_string.parse()?; + + let mut granted_permission = GrantedPermission::default(); + + for node in kdl_document.nodes() { + if let Some(children) = node.children() { + let key = kdl_name!(node); + let permissions: Vec<PermissionType> = children + .nodes() + .iter() + .filter_map(|p| { + let v = kdl_name!(p); + PermissionType::from_str(v).ok() + }) + .collect(); + + granted_permission.insert(key.into(), permissions); + } + } + + Ok(granted_permission) + } + + pub fn to_string(granted: &GrantedPermission) -> String { + let mut kdl_doucment = KdlDocument::new(); + + granted.iter().for_each(|(k, v)| { + let mut node = KdlNode::new(k.as_str()); + let mut children = KdlDocument::new(); + + let permissions: HashSet<PermissionType> = v.clone().into_iter().collect(); + permissions.iter().for_each(|f| { + let n = KdlNode::new(f.to_string().as_str()); + children.nodes_mut().push(n); + }); + + node.set_children(children); + kdl_doucment.nodes_mut().push(node); + }); + + kdl_doucment.fmt(); + kdl_doucment.to_string() + } +} + pub fn parse_plugin_user_configuration( plugin_block: &KdlNode, ) -> Result<BTreeMap<String, String>, ConfigError> { diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index 95928ae3b..3a9838fa8 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -38,6 +38,7 @@ enum EventType { FileSystemUpdate = 13; /// A file was deleted somewhere in the Zellij CWD folder FileSystemDelete = 14; + PermissionRequestResult = 15; } message EventNameList { @@ -57,9 +58,14 @@ message Event { bool visible_payload = 9; CustomMessagePayload custom_message_payload = 10; FileListPayload file_list_payload = 11; + PermissionRequestResultPayload permission_request_result_payload = 12; } } +message PermissionRequestResultPayload { + bool granted = 1; +} + message FileListPayload { repeated string paths = 1; } diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index 315fa54b8..6115fe1e8 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -13,9 +13,10 @@ pub use super::generated_api::api::{ style::Style as ProtobufStyle, }; use crate::data::{ - CopyDestination, Direction, Event, EventType, InputMode, Key, ModeInfo, Mouse, Palette, - PaletteColor, PaneInfo, PaneManifest, PluginCapabilities, Style, TabInfo, ThemeHue, + CopyDestination, Event, EventType, InputMode, Key, ModeInfo, Mouse, PaneInfo, PaneManifest, + PermissionStatus, PluginCapabilities, Style, TabInfo, }; + use crate::errors::prelude::*; use crate::input::actions::Action; @@ -160,6 +161,16 @@ impl TryFrom<ProtobufEvent> for Event { }, _ => Err("Malformed payload for the file system delete Event"), }, + Some(ProtobufEventType::PermissionRequestResult) => match protobuf_event.payload { + Some(ProtobufEventPayload::PermissionRequestResultPayload(payload)) => { + if payload.granted { + Ok(Event::PermissionRequestResult(PermissionStatus::Granted)) + } else { + Ok(Event::PermissionRequestResult(PermissionStatus::Denied)) + } + }, + _ => Err("Malformed payload for the file system delete Event"), + }, None => Err("Unknown Protobuf Event"), } } @@ -290,6 +301,18 @@ impl TryFrom<Event> for ProtobufEvent { payload: Some(event::Payload::FileListPayload(file_list_payload)), }) }, + Event::PermissionRequestResult(permission_status) => { + let granted = match permission_status { + PermissionStatus::Granted => true, + PermissionStatus::Denied => false, + }; + Ok(ProtobufEvent { + name: ProtobufEventType::PermissionRequestResult as i32, + payload: Some(event::Payload::PermissionRequestResultPayload( + PermissionRequestResultPayload { granted }, + )), + }) + }, } } } @@ -673,6 +696,7 @@ impl TryFrom<ProtobufEventType> for EventType { ProtobufEventType::FileSystemRead => EventType::FileSystemRead, ProtobufEventType::FileSystemUpdate => EventType::FileSystemUpdate, ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete, + ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult, }) } } @@ -696,6 +720,7 @@ impl TryFrom<EventType> for ProtobufEventType { EventType::FileSystemRead => ProtobufEventType::FileSystemRead, EventType::FileSystemUpdate => ProtobufEventType::FileSystemUpdate, EventType::FileSystemDelete => ProtobufEventType::FileSystemDelete, + EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult, }) } } @@ -717,6 +742,7 @@ fn serialize_mode_update_event() { #[test] fn serialize_mode_update_event_with_non_default_values() { + use crate::data::{Direction, Palette, PaletteColor, ThemeHue}; use prost::Message; let mode_update_event = Event::ModeUpdate(ModeInfo { mode: InputMode::Locked, diff --git a/zellij-utils/src/plugin_api/mod.rs b/zellij-utils/src/plugin_api/mod.rs index 55ace5007..4812d468c 100644 --- a/zellij-utils/src/plugin_api/mod.rs +++ b/zellij-utils/src/plugin_api/mod.rs @@ -7,6 +7,7 @@ pub mod key; pub mod message; pub mod plugin_command; pub mod plugin_ids; +pub mod plugin_permission; pub mod resize; pub mod style; pub mod generated_api { diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index f09ccf4b1..9ba8bb7f4 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -6,6 +6,7 @@ import "command.proto"; import "message.proto"; import "input_mode.proto"; import "resize.proto"; +import "plugin_permission.proto"; package api.plugin_command; @@ -76,6 +77,7 @@ enum CommandName { RenamePluginPane = 63; RenameTab = 64; ReportCrash = 65; + RequestPluginPermissions = 66; } message PluginCommand { @@ -117,9 +119,14 @@ message PluginCommand { IdAndNewName rename_plugin_pane_payload = 35; IdAndNewName rename_tab_payload = 36; string report_crash_payload = 37; + RequestPluginPermissionPayload request_plugin_permission_payload = 38; } } +message RequestPluginPermissionPayload { + repeated plugin_permission.PermissionType permissions = 1; +} + message SubscribePayload { event.EventNameList subscriptions = 1; } diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index bf359dd20..ff50a6a76 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -3,14 +3,15 @@ pub use super::generated_api::api::{ plugin_command::{ plugin_command::Payload, CommandName, ExecCmdPayload, IdAndNewName, MovePayload, OpenCommandPanePayload, OpenFilePayload, PaneIdAndShouldFloat, - PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ResizePayload, - SetTimeoutPayload, SubscribePayload, SwitchTabToPayload, SwitchToModePayload, - UnsubscribePayload, + PluginCommand as ProtobufPluginCommand, PluginMessagePayload, + RequestPluginPermissionPayload, ResizePayload, SetTimeoutPayload, SubscribePayload, + SwitchTabToPayload, SwitchToModePayload, UnsubscribePayload, }, + plugin_permission::PermissionType as ProtobufPermissionType, resize::ResizeAction as ProtobufResizeAction, }; -use crate::data::PluginCommand; +use crate::data::{PermissionType, PluginCommand}; use std::convert::TryFrom; @@ -486,6 +487,19 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand { }, _ => Err("Mismatched payload for ReportCrash"), }, + Some(CommandName::RequestPluginPermissions) => match protobuf_plugin_command.payload { + Some(Payload::RequestPluginPermissionPayload(payload)) => { + Ok(PluginCommand::RequestPluginPermissions( + payload + .permissions + .iter() + .filter_map(|p| ProtobufPermissionType::from_i32(*p)) + .filter_map(|p| PermissionType::try_from(p).ok()) + .collect(), + )) + }, + _ => Err("Mismatched payload for RequestPluginPermission"), + }, None => Err("Unrecognized plugin command"), } } @@ -820,6 +834,18 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand { name: CommandName::ReportCrash as i32, payload: Some(Payload::ReportCrashPayload(payload)), }), + Pl |