summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJae-Heon Ji <32578710+jaeheonji@users.noreply.github.com>2023-08-12 22:35:42 +0900
committerGitHub <noreply@github.com>2023-08-12 15:35:42 +0200
commitc8ddb23297e2f4fc900b8286d57e2808ae6a4fdb (patch)
treec96d26c8bfc24172374aefe88624b78c6890663f
parenta1903b6b048f8257fc16ffd09e19c825248cb9d6 (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>
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs13
-rw-r--r--default-plugins/strider/src/main.rs4
-rw-r--r--zellij-server/src/panes/floating_panes/mod.rs8
-rw-r--r--zellij-server/src/panes/plugin_pane.rs101
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs2
-rw-r--r--zellij-server/src/plugins/mod.rs36
-rw-r--r--zellij-server/src/plugins/plugin_loader.rs28
-rw-r--r--zellij-server/src/plugins/plugin_map.rs7
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs1151
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__denied_permission_request_result.snap7
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap15
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap17
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command_permission_denied.snap6
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs138
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs329
-rw-r--r--zellij-server/src/screen.rs27
-rw-r--r--zellij-server/src/tab/mod.rs54
-rw-r--r--zellij-tile/src/lib.rs1
-rw-r--r--zellij-tile/src/shim.rs7
-rwxr-xr-xzellij-utils/assets/plugins/compact-bar.wasmbin526730 -> 806599 bytes
-rwxr-xr-xzellij-utils/assets/plugins/fixture-plugin-for-tests.wasmbin469401 -> 768790 bytes
-rwxr-xr-xzellij-utils/assets/plugins/status-bar.wasmbin656048 -> 961879 bytes
-rwxr-xr-xzellij-utils/assets/plugins/strider.wasmbin1718164 -> 2025837 bytes
-rwxr-xr-xzellij-utils/assets/plugins/tab-bar.wasmbin497583 -> 781434 bytes
-rw-r--r--zellij-utils/src/consts.rs2
-rw-r--r--zellij-utils/src/data.rs70
-rw-r--r--zellij-utils/src/errors.rs2
-rw-r--r--zellij-utils/src/input/mod.rs1
-rw-r--r--zellij-utils/src/input/permission.rs60
-rw-r--r--zellij-utils/src/kdl/mod.rs52
-rw-r--r--zellij-utils/src/plugin_api/event.proto6
-rw-r--r--zellij-utils/src/plugin_api/event.rs30
-rw-r--r--zellij-utils/src/plugin_api/mod.rs1
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto7
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs34
-rw-r--r--zellij-utils/src/plugin_api/plugin_permission.proto12
-rw-r--r--zellij-utils/src/plugin_api/plugin_permission.rs44
37 files changed, 1935 insertions, 337 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index fe8b034b9..811a4507e 100644
--- a/default-plugins/fixture-plugin-for-tests/src/main.rs
+++ b/default-plugins/fixture-plugin-for-tests/src/main.rs
@@ -39,6 +39,16 @@ register_worker!(TestWorker, test_worker, TEST_WORKER);
impl ZellijPlugin for State {
fn load(&mut self, configuration: BTreeMap<String, String>) {
+ request_permission(&[
+ PermissionType::ChangeApplicationState,
+ PermissionType::ReadApplicationState,
+ PermissionType::ReadApplicationState,
+ PermissionType::ChangeApplicationState,
+ PermissionType::OpenFiles,
+ PermissionType::RunCommands,
+ PermissionType::OpenTerminalsOrPlugins,
+ PermissionType::WriteToStdin,
+ ]);
self.configuration = configuration;
subscribe(&[
EventType::InputReceived,
@@ -227,6 +237,9 @@ impl ZellijPlugin for State {
Key::Ctrl('z') => {
go_to_tab_name(&format!("{:?}", self.configuration));
},
+ Key::Ctrl('1') => {
+ request_permission(&[PermissionType::ReadApplicationState]);
+ },
_ => {},
},
Event::CustomMessage(message, payload) => {
diff --git a/default-plugins/strider/src/main.rs b/default-plugins/strider/src/main.rs
index e620930c0..3d4c94a91 100644
--- a/default-plugins/strider/src/main.rs
+++ b/default-plugins/strider/src/main.rs
@@ -30,6 +30,7 @@ impl ZellijPlugin for State {
EventType::FileSystemCreate,
EventType::FileSystemUpdate,
EventType::FileSystemDelete,
+ EventType::PermissionRequestResult,
]);
post_message_to(PluginMessage {
worker_name: Some("file_name_search".into()),
@@ -54,6 +55,9 @@ impl ZellijPlugin for State {
};
self.ev_history.push_back((event.clone(), Instant::now()));
match event {
+ Event::PermissionRequestResult(_) => {
+ should_render = true;
+ },
Event::Timer(_elapsed) => {
if self.search_state.loading {
set_timeout(0.5);
diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs
index 2de5b74eb..1f2d770bb 100644
--- a/zellij-server/src/panes/floating_panes/mod.rs
+++ b/zellij-server/src/panes/floating_panes/mod.rs
@@ -295,7 +295,7 @@ impl FloatingPanes {
pane.render_full_viewport();
}
}
- pub fn set_pane_frames(&mut self, os_api: &mut Box<dyn ServerOsApi>) -> Result<()> {
+ pub fn set_pane_frames(&mut self, _os_api: &mut Box<dyn ServerOsApi>) -> Result<()> {
let err_context =
|pane_id: &PaneId| format!("failed to activate frame on pane {pane_id:?}");
@@ -392,7 +392,7 @@ impl FloatingPanes {
self.set_force_render();
}
- pub fn resize_pty_all_panes(&mut self, os_api: &mut Box<dyn ServerOsApi>) -> Result<()> {
+ pub fn resize_pty_all_panes(&mut self, _os_api: &mut Box<dyn ServerOsApi>) -> Result<()> {
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api, self.senders, self.character_cell_size)
.with_context(|| format!("failed to resize PTY in pane {:?}", pane.pid()))?;
@@ -403,7 +403,7 @@ impl FloatingPanes {
pub fn resize_active_pane(
&mut self,
client_id: ClientId,
- os_api: &mut Box<dyn ServerOsApi>,
+ _os_api: &mut Box<dyn ServerOsApi>,
strategy: &ResizeStrategy,
) -> Result<bool> {
// true => successfully resized
@@ -838,7 +838,7 @@ impl FloatingPanes {
self.focus_pane_for_all_clients(focused_pane);
}
}
- pub fn switch_active_pane_with(&mut self, os_api: &mut Box<dyn ServerOsApi>, pane_id: PaneId) {
+ pub fn switch_active_pane_with(&mut self, _os_api: &mut Box<dyn ServerOsApi>, pane_id: PaneId) {
if let Some(active_pane_id) = self.first_active_floating_pane_id() {
let current_position = self.panes.get(&active_pane_id).unwrap();
let prev_geom = current_position.position_and_size();
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index 465529609..b4e3461e2 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -1,11 +1,11 @@
-use std::collections::HashMap;
+use std::collections::{BTreeSet, HashMap};
use std::time::Instant;
use crate::output::{CharacterChunk, SixelImageChunk};
use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId};
use crate::plugins::PluginInstruction;
use crate::pty::VteBytes;
-use crate::tab::Pane;
+use crate::tab::{AdjustedInput, Pane};
use crate::ui::{
loading_indication::LoadingIndication,
pane_boundaries_frame::{FrameParams, PaneFrame},
@@ -13,6 +13,7 @@ use crate::ui::{
use crate::ClientId;
use std::cell::RefCell;
use std::rc::Rc;
+use zellij_utils::data::{PermissionStatus, PermissionType, PluginPermission};
use zellij_utils::pane_size::{Offset, SizeInPixels};
use zellij_utils::position::Position;
use zellij_utils::{
@@ -25,6 +26,15 @@ use zellij_utils::{
vte,
};
+macro_rules! style {
+ ($fg:expr) => {
+ ansi_term::Style::new().fg(match $fg {
+ PaletteColor::Rgb((r, g, b)) => ansi_term::Color::RGB(r, g, b),
+ PaletteColor::EightBit(color) => ansi_term::Color::Fixed(color),
+ })
+ };
+}
+
macro_rules! get_or_create_grid {
($self:ident, $client_id:ident) => {{
let rows = $self.get_content_rows();
@@ -73,6 +83,7 @@ pub(crate) struct PluginPane {
pane_frame_color_override: Option<(PaletteColor, Option<String>)>,
invoked_with: Option<Run>,
loading_indication: LoadingIndication,
+ requesting_permissions: Option<PluginPermission>,
debug: bool,
}
@@ -121,6 +132,7 @@ impl PluginPane {
pane_frame_color_override: None,
invoked_with,
loading_indication,
+ requesting_permissions: None,
debug,
};
for client_id in currently_connected_clients {
@@ -181,6 +193,14 @@ impl Pane for PluginPane {
}
fn handle_plugin_bytes(&mut self, client_id: ClientId, bytes: VteBytes) {
self.set_client_should_render(client_id, true);
+
+ let mut vte_bytes = bytes;
+ if let Some(plugin_permission) = &self.requesting_permissions {
+ vte_bytes = self
+ .display_request_permission_message(plugin_permission)
+ .into();
+ }
+
let grid = get_or_create_grid!(self, client_id);
// this is part of the plugin contract, whenever we update the plugin and call its render function, we delete the existing viewport
@@ -193,14 +213,36 @@ impl Pane for PluginPane {
.vte_parsers
.entry(client_id)
.or_insert_with(|| vte::Parser::new());
- for &byte in &bytes {
+
+ for &byte in &vte_bytes {
vte_parser.advance(grid, byte);
}
+
self.should_render.insert(client_id, true);
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
None
}
+ fn adjust_input_to_terminal(&mut self, input_bytes: Vec<u8>) -> Option<AdjustedInput> {
+ if let Some(requesting_permissions) = &self.requesting_permissions {
+ let permissions = requesting_permissions.permissions.clone();
+ match input_bytes.as_slice() {
+ // Y or y
+ &[89] | &[121] => Some(AdjustedInput::PermissionRequestResult(
+ permissions,
+ PermissionStatus::Granted,
+ )),
+ // N or n
+ &[78] | &[110] => Some(AdjustedInput::PermissionRequestResult(
+ permissions,
+ PermissionStatus::Denied,
+ )),
+ _ => None,
+ }
+ } else {
+ Some(AdjustedInput::WriteBytesToTerminal(input_bytes))
+ }
+ }
fn position_and_size(&self) -> PaneGeom {
self.geom
}
@@ -233,6 +275,9 @@ impl Pane for PluginPane {
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
+ fn request_permissions_from_user(&mut self, permissions: Option<PluginPermission>) {
+ self.requesting_permissions = permissions;
+ }
fn render(
&mut self,
client_id: Option<ClientId>,
@@ -595,4 +640,54 @@ impl PluginPane {
self.handle_plugin_bytes(client_id, bytes.clone());
}
}
+ fn display_request_permission_message(&self, plugin_permission: &PluginPermission) -> String {
+ let bold_white = style!(self.style.colors.white).bold();
+ let cyan = style!(self.style.colors.cyan).bold();
+ let orange = style!(self.style.colors.orange).bold();
+ let green = style!(self.style.colors.green).bold();
+
+ let mut messages = String::new();
+ let permissions: BTreeSet<PermissionType> =
+ plugin_permission.permissions.clone().into_iter().collect();
+
+ let min_row_count = permissions.len() + 4;
+
+ if self.rows() >= min_row_count {
+ messages.push_str(&format!(
+ "{} {} {}\n",
+ bold_white.paint("Plugin"),
+ cyan.paint(&plugin_permission.name),
+ bold_white.paint("asks permission to:"),
+ ));
+ permissions.iter().enumerate().for_each(|(i, p)| {
+ messages.push_str(&format!(
+ "\n\r{}. {}",
+ bold_white.paint(&format!("{}", i + 1)),
+ orange.paint(p.display_name())
+ ));
+ });
+
+ messages.push_str(&format!(
+ "\n\n\r{} {}",
+ bold_white.paint("Allow?"),
+ green.paint("(y/n)"),
+ ));
+ } else {
+ messages.push_str(&format!(
+ "{} {}. {} {}\n",
+ bold_white.paint("This plugin asks permission to:"),
+ orange.paint(
+ permissions
+ .iter()
+ .map(|p| p.to_string())
+ .collect::<Vec<_>>()
+ .join(", ")
+ ),
+ bold_white.paint("Allow?"),
+ green.paint("(y/n)"),
+ ));
+ }
+
+ messages
+ }
}
diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs
index 9aca1af4a..736c16bc9 100644
--- a/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/zellij-server/src/panes/tiled_panes/mod.rs
@@ -68,7 +68,6 @@ pub struct TiledPanes {
draw_pane_frames: bool,
panes_to_hide: HashSet<PaneId>,
fullscreen_is_active: bool,
- os_api: Box<dyn ServerOsApi>,
senders: ThreadSenders,
window_title: Option<String>,
client_id_to_boundaries: HashMap<ClientId, Boundaries>,
@@ -105,7 +104,6 @@ impl TiledPanes {
draw_pane_frames,
panes_to_hide: HashSet::new(),
fullscreen_is_active: false,
- os_api,
senders,
window_title: None,
client_id_to_boundaries: HashMap::new(),
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index f36bdc2d2..4bef0683d 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -18,7 +18,7 @@ use crate::{pty::PtyInstruction, thread_bus::Bus, ClientId, ServerInstruction};
use wasm_bridge::WasmBridge;
use zellij_utils::{
- data::{Event, EventType, PluginCapabilities},
+ data::{Event, EventType, PermissionStatus, PermissionType, PluginCapabilities},
errors::{prelude::*, ContextType, PluginContext},
input::{
command::TerminalAction,
@@ -79,6 +79,13 @@ pub enum PluginInstruction {
String, // serialized payload
),
PluginSubscribedToEvents(PluginId, ClientId, HashSet<EventType>),
+ PermissionRequestResult(
+ PluginId,
+ Option<ClientId>,
+ Vec<PermissionType>,
+ PermissionStatus,
+ Option<PathBuf>,
+ ),
Exit,
}
@@ -105,6 +112,9 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::PluginSubscribedToEvents(..) => {
PluginContext::PluginSubscribedToEvents
},
+ PluginInstruction::PermissionRequestResult(..) => {
+ PluginContext::PermissionRequestResult
+ },
}
}
}
@@ -287,6 +297,30 @@ pub(crate) fn plugin_thread_main(
}
}
},
+ PluginInstruction::PermissionRequestResult(
+ plugin_id,
+ client_id,
+ permissions,
+ status,
+ cache_path,
+ ) => {
+ if let Err(e) = wasm_bridge.cache_plugin_permissions(
+ plugin_id,
+ client_id,
+ permissions,
+ status,
+ cache_path,
+ ) {
+ log::error!("{}", e);
+ }
+
+ let updates = vec![(
+ Some(plugin_id),
+ client_id,
+ Event::PermissionRequestResult(status),
+ )];
+ wasm_bridge.update_plugins(updates)?;
+ },
PluginInstruction::Exit => {
wasm_bridge.cleanup();
break;
diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs
index 92a4c0480..ea0c2b6a6 100644
--- a/zellij-server/src/plugins/plugin_loader.rs
+++ b/zellij-server/src/plugins/plugin_loader.rs
@@ -188,6 +188,7 @@ impl<'a> PluginLoader<'a> {
display_loading_stage!(end, loading_indication, senders, plugin_id);
Ok(())
}
+
pub fn add_client(
client_id: ClientId,
plugin_dir: PathBuf,
@@ -613,6 +614,19 @@ impl<'a> PluginLoader<'a> {
}
start_function.call(&[]).with_context(err_context)?;
+ plugin_map.lock().unwrap().insert(
+ self.plugin_id,
+ self.client_id,
+ Arc::new(Mutex::new(RunningPlugin::new(
+ main_user_instance,
+ main_user_env,
+ self.size.rows,
+ self.size.cols,
+ ))),
+ subscriptions.clone(),
+ workers,
+ );
+
let protobuf_plugin_configuration: ProtobufPluginConfiguration = self
.plugin
.userspace_configuration
@@ -640,18 +654,6 @@ impl<'a> PluginLoader<'a> {
self.senders,
self.plugin_id
);
- plugin_map.lock().unwrap().insert(
- self.plugin_id,
- self.client_id,
- Arc::new(Mutex::new(RunningPlugin::new(
- main_user_instance,
- main_user_env,
- self.size.rows,
- self.size.cols,
- ))),
- subscriptions.clone(),
- workers,
- );
display_loading_stage!(
indicate_writing_plugin_to_cache_success,