Skip to content

Commit

Permalink
feat: add plugin permission system (#2624)
Browse files Browse the repository at this point in the history
* 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 a4e9370.

* 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 01eff3b.

* 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>
  • Loading branch information
6 people authored Aug 12, 2023
1 parent a1903b6 commit c8ddb23
Show file tree
Hide file tree
Showing 37 changed files with 1,935 additions and 337 deletions.
13 changes: 13 additions & 0 deletions default-plugins/fixture-plugin-for-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) => {
Expand Down
4 changes: 4 additions & 0 deletions default-plugins/strider/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand All @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions zellij-server/src/panes/floating_panes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:?}");

Expand Down Expand Up @@ -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()))?;
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
101 changes: 98 additions & 3 deletions zellij-server/src/panes/plugin_pane.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
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},
};
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::{
Expand All @@ -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();
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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
}
}
2 changes: 0 additions & 2 deletions zellij-server/src/panes/tiled_panes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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(),
Expand Down
36 changes: 35 additions & 1 deletion zellij-server/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}

Expand All @@ -105,6 +112,9 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::PluginSubscribedToEvents(..) => {
PluginContext::PluginSubscribedToEvents
},
PluginInstruction::PermissionRequestResult(..) => {
PluginContext::PermissionRequestResult
},
}
}
}
Expand Down Expand Up @@ -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;
Expand Down
28 changes: 15 additions & 13 deletions zellij-server/src/plugins/plugin_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
self.loading_indication,
Expand Down Expand Up @@ -764,13 +766,13 @@ impl<'a> PluginLoader<'a> {
})
.with_context(err_context)?;
let wasi = wasi_env.import_object(&module).with_context(err_context)?;

let mut mut_plugin = self.plugin.clone();
mut_plugin.set_tab_index(self.tab_index);
let plugin_env = PluginEnv {
plugin_id: self.plugin_id,
client_id: self.client_id,
plugin: mut_plugin,
permissions: Arc::new(Mutex::new(None)),
senders: self.senders.clone(),
wasi_env,
plugin_own_data_dir: self.plugin_own_data_dir.clone(),
Expand Down
Loading

0 comments on commit c8ddb23

Please sign in to comment.