diff --git a/dev_plugin/gauntlet.toml b/dev_plugin/gauntlet.toml index 9d36add..6b8018e 100644 --- a/dev_plugin/gauntlet.toml +++ b/dev_plugin/gauntlet.toml @@ -147,4 +147,5 @@ os = 'windows' [permissions] environment = ["RUST_LOG"] system = ["systemMemoryInfo"] -network = ["upload.wikimedia.org", "api.github.com"] \ No newline at end of file +network = ["upload.wikimedia.org", "api.github.com"] +clipboard = ["read", "write", "clear"] \ No newline at end of file diff --git a/rust/server/src/plugins/data_db_repository.rs b/rust/server/src/plugins/data_db_repository.rs index f447851..b868ced 100644 --- a/rust/server/src/plugins/data_db_repository.rs +++ b/rust/server/src/plugins/data_db_repository.rs @@ -114,14 +114,34 @@ pub enum DbPluginType { #[derive(Debug, Deserialize, Serialize)] pub struct DbPluginPermissions { + #[serde(default)] pub environment: Vec, + #[serde(default)] pub high_resolution_time: bool, + #[serde(default)] pub network: Vec, + #[serde(default)] pub ffi: Vec, + #[serde(default)] pub fs_read_access: Vec, + #[serde(default)] pub fs_write_access: Vec, + #[serde(default)] pub run_subprocess: Vec, + #[serde(default)] pub system: Vec, + #[serde(default)] + pub clipboard: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum DbPluginClipboardPermissions { + #[serde(rename = "read")] + Read, + #[serde(rename = "write")] + Write, + #[serde(rename = "clear")] + Clear } #[derive(Debug, Deserialize, Serialize)] diff --git a/rust/server/src/plugins/js/clipboard.rs b/rust/server/src/plugins/js/clipboard.rs index ec0b6d5..a013193 100644 --- a/rust/server/src/plugins/js/clipboard.rs +++ b/rust/server/src/plugins/js/clipboard.rs @@ -1,11 +1,13 @@ +use std::cell::RefCell; use std::io::Cursor; - +use std::rc::Rc; use anyhow::anyhow; use arboard::ImageData; -use deno_core::op; +use deno_core::{op, OpState}; use image::RgbaImage; use serde::{Deserialize, Serialize}; use tokio::task::spawn_blocking; +use crate::plugins::js::{PluginClipboardPermissions, PluginData}; fn unknown_err_clipboard(err: arboard::Error) -> anyhow::Error { anyhow!("UNKNOWN_ERROR: {:?}", err) @@ -26,7 +28,21 @@ struct ClipboardData { } #[op] -async fn clipboard_read() -> anyhow::Result { +async fn clipboard_read(state: Rc>) -> anyhow::Result { + { + let state = state.borrow(); + + let allow = state + .borrow::() + .permissions() + .clipboard + .contains(&PluginClipboardPermissions::Read); + + if !allow { + return Err(anyhow!("Plugin doesn't have 'read' permission for clipboard")); + } + } + spawn_blocking(|| { let mut clipboard = arboard::Clipboard::new() .map_err(|err| unknown_err_clipboard(err))?; @@ -74,7 +90,21 @@ async fn clipboard_read() -> anyhow::Result { #[op] -async fn clipboard_read_text() -> anyhow::Result> { +async fn clipboard_read_text(state: Rc>) -> anyhow::Result> { + { + let state = state.borrow(); + + let allow = state + .borrow::() + .permissions() + .clipboard + .contains(&PluginClipboardPermissions::Read); + + if !allow { + return Err(anyhow!("Plugin doesn't have 'read' permission for clipboard")); + } + } + spawn_blocking(|| { let mut clipboard = arboard::Clipboard::new() .map_err(|err| unknown_err_clipboard(err))?; @@ -96,7 +126,21 @@ async fn clipboard_read_text() -> anyhow::Result> { } #[op] -async fn clipboard_write(data: ClipboardData) -> anyhow::Result<()> { // TODO deserialization broken, fix when migrating to deno's op2 +async fn clipboard_write(state: Rc>, data: ClipboardData) -> anyhow::Result<()> { // TODO deserialization broken, fix when migrating to deno's op2 + { + let state = state.borrow(); + + let allow = state + .borrow::() + .permissions() + .clipboard + .contains(&PluginClipboardPermissions::Write); + + if !allow { + return Err(anyhow!("Plugin doesn't have 'write' permission for clipboard")); + } + } + spawn_blocking(|| { let mut clipboard = arboard::Clipboard::new() .map_err(|err| unknown_err_clipboard(err))?; @@ -134,7 +178,21 @@ async fn clipboard_write(data: ClipboardData) -> anyhow::Result<()> { // TODO de } #[op] -async fn clipboard_write_text(data: String) -> anyhow::Result<()> { +async fn clipboard_write_text(state: Rc>, data: String) -> anyhow::Result<()> { + { + let state = state.borrow(); + + let allow = state + .borrow::() + .permissions() + .clipboard + .contains(&PluginClipboardPermissions::Write); + + if !allow { + return Err(anyhow!("Plugin doesn't have 'write' permission for clipboard")); + } + } + spawn_blocking(|| { let mut clipboard = arboard::Clipboard::new() .map_err(|err| unknown_err_clipboard(err))?; @@ -147,7 +205,21 @@ async fn clipboard_write_text(data: String) -> anyhow::Result<()> { } #[op] -async fn clipboard_clear() -> anyhow::Result<()> { +async fn clipboard_clear(state: Rc>) -> anyhow::Result<()> { + { + let state = state.borrow(); + + let allow = state + .borrow::() + .permissions() + .clipboard + .contains(&PluginClipboardPermissions::Clear); + + if !allow { + return Err(anyhow!("Plugin doesn't have 'clear' permission for clipboard")); + } + } + spawn_blocking(|| { let mut clipboard = arboard::Clipboard::new() .map_err(|err| unknown_err_clipboard(err))?; diff --git a/rust/server/src/plugins/js/mod.rs b/rust/server/src/plugins/js/mod.rs index 574d934..c1c2c59 100644 --- a/rust/server/src/plugins/js/mod.rs +++ b/rust/server/src/plugins/js/mod.rs @@ -31,7 +31,7 @@ use component_model::{Children, Component, create_component_model, Property, Pro use crate::model::{IntermediateUiEvent, JsUiEvent, JsUiPropertyValue, JsUiRenderLocation, JsUiRequestData, JsUiResponseData, JsUiWidget, PreferenceUserData}; use crate::plugins::applications::{DesktopEntry, get_apps}; -use crate::plugins::data_db_repository::{DataDbRepository, db_entrypoint_from_str, DbPluginEntrypointType, DbPluginPreference, DbPluginPreferenceUserData, DbReadPlugin, DbReadPluginEntrypoint}; +use crate::plugins::data_db_repository::{DataDbRepository, db_entrypoint_from_str, DbPluginEntrypointType, DbPluginPreference, DbPluginPreferenceUserData, DbReadPlugin, DbReadPluginEntrypoint, DbPluginClipboardPermissions}; use crate::plugins::icon_cache::IconCache; use crate::plugins::js::assets::{asset_data, asset_data_blocking}; use crate::plugins::js::clipboard::{clipboard_clear, clipboard_read, clipboard_read_text, clipboard_write, clipboard_write_text}; @@ -82,8 +82,22 @@ pub struct PluginPermissions { pub fs_write_access: Vec, pub run_subprocess: Vec, pub system: Vec, + pub clipboard: Vec, } +#[derive(Clone, Debug)] +pub struct PluginRuntimePermissions { + pub clipboard: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PluginClipboardPermissions { + Read, + Write, + Clear +} + + #[derive(Clone, Debug)] pub enum PluginCommand { One { @@ -340,6 +354,10 @@ async fn start_js_runtime( None }; + let runtime_permissions = PluginRuntimePermissions { + clipboard: permissions.clipboard, + }; + let mut worker = MainWorker::bootstrap_from_options( unused_url, permissions_container, @@ -347,7 +365,7 @@ async fn start_js_runtime( module_loader: Rc::new(CustomModuleLoader::new(code, dev_plugin)), extensions: vec![plugin_ext::init_ops_and_esm( EventReceiver::new(event_stream), - PluginData::new(plugin_id, plugin_uuid, inline_view_entrypoint_id), + PluginData::new(plugin_id, plugin_uuid, inline_view_entrypoint_id, runtime_permissions), frontend_api, ComponentModel::new(component_model), repository, @@ -682,15 +700,17 @@ fn from_intermediate_to_js_event(event: IntermediateUiEvent) -> JsUiEvent { pub struct PluginData { plugin_id: PluginId, plugin_uuid: String, - inline_view_entrypoint_id: Option + inline_view_entrypoint_id: Option, + permissions: PluginRuntimePermissions } impl PluginData { - fn new(plugin_id: PluginId, plugin_uuid: String, inline_view_entrypoint_id: Option) -> Self { + fn new(plugin_id: PluginId, plugin_uuid: String, inline_view_entrypoint_id: Option, permissions: PluginRuntimePermissions) -> Self { Self { plugin_id, plugin_uuid, - inline_view_entrypoint_id + inline_view_entrypoint_id, + permissions } } @@ -705,6 +725,10 @@ impl PluginData { fn inline_view_entrypoint_id(&self) -> Option { self.inline_view_entrypoint_id.clone() } + + fn permissions(&self) -> &PluginRuntimePermissions { + &self.permissions + } } pub struct ComponentModel { diff --git a/rust/server/src/plugins/loader.rs b/rust/server/src/plugins/loader.rs index 332d64c..e25c333 100644 --- a/rust/server/src/plugins/loader.rs +++ b/rust/server/src/plugins/loader.rs @@ -14,7 +14,7 @@ use itertools::Itertools; use tracing_subscriber::fmt::format; use common::model::{DownloadStatus, PluginId}; use crate::model::ActionShortcutKey; -use crate::plugins::data_db_repository::{DataDbRepository, db_entrypoint_to_str, db_plugin_type_to_str, DbCode, DbPluginAction, DbPluginActionShortcutKind, DbPluginEntrypointType, DbPluginPermissions, DbPluginPreference, DbPluginPreferenceUserData, DbPluginType, DbPreferenceEnumValue, DbWritePlugin, DbWritePluginAssetData, DbWritePluginEntrypoint}; +use crate::plugins::data_db_repository::{DataDbRepository, db_entrypoint_to_str, db_plugin_type_to_str, DbCode, DbPluginAction, DbPluginActionShortcutKind, DbPluginEntrypointType, DbPluginPermissions, DbPluginPreference, DbPluginPreferenceUserData, DbPluginType, DbPreferenceEnumValue, DbWritePlugin, DbWritePluginAssetData, DbWritePluginEntrypoint, DbPluginClipboardPermissions}; use crate::plugins::download_status::DownloadStatusHolder; pub struct PluginLoader { @@ -321,6 +321,18 @@ impl PluginLoader { }) .collect(); + let clipboard = plugin_manifest.permissions + .clipboard + .into_iter() + .map(|permission| { + match permission { + PluginManifestClipboardPermissions::Read => DbPluginClipboardPermissions::Read, + PluginManifestClipboardPermissions::Write => DbPluginClipboardPermissions::Write, + PluginManifestClipboardPermissions::Clear => DbPluginClipboardPermissions::Clear, + } + }) + .collect(); + let permissions = DbPluginPermissions { environment: plugin_manifest.permissions.environment, high_resolution_time: plugin_manifest.permissions.high_resolution_time, @@ -330,6 +342,7 @@ impl PluginLoader { fs_write_access: plugin_manifest.permissions.fs_write_access, run_subprocess: plugin_manifest.permissions.run_subprocess, system: plugin_manifest.permissions.system, + clipboard, }; Ok(PluginDownloadData { @@ -814,4 +827,16 @@ pub struct PluginManifestPermissions { run_subprocess: Vec, #[serde(default)] system: Vec, + #[serde(default)] + clipboard: Vec, +} + +#[derive(Debug, Deserialize)] +pub enum PluginManifestClipboardPermissions { + #[serde(rename = "read")] + Read, + #[serde(rename = "write")] + Write, + #[serde(rename = "clear")] + Clear } diff --git a/rust/server/src/plugins/mod.rs b/rust/server/src/plugins/mod.rs index 706ccf3..42a3d16 100644 --- a/rust/server/src/plugins/mod.rs +++ b/rust/server/src/plugins/mod.rs @@ -17,10 +17,10 @@ use utils::channel::RequestSender; use common::dirs::Dirs; use crate::model::ActionShortcutKey; use crate::plugins::config_reader::ConfigReader; -use crate::plugins::data_db_repository::{DataDbRepository, db_entrypoint_from_str, DbPluginActionShortcutKind, DbPluginEntrypointType, DbPluginPreference, DbPluginPreferenceUserData, DbReadPluginEntrypoint}; +use crate::plugins::data_db_repository::{DataDbRepository, db_entrypoint_from_str, DbPluginActionShortcutKind, DbPluginEntrypointType, DbPluginPreference, DbPluginPreferenceUserData, DbReadPluginEntrypoint, DbPluginClipboardPermissions}; use crate::plugins::global_shortcut::{convert_physical_shortcut_to_hotkey, register_listener}; use crate::plugins::icon_cache::IconCache; -use crate::plugins::js::{AllPluginCommandData, OnePluginCommandData, PluginCode, PluginCommand, PluginPermissions, PluginRuntimeData, start_plugin_runtime}; +use crate::plugins::js::{AllPluginCommandData, OnePluginCommandData, PluginCode, PluginCommand, PluginPermissions, PluginRuntimeData, start_plugin_runtime, PluginClipboardPermissions}; use crate::plugins::loader::PluginLoader; use crate::plugins::run_status::RunStatusHolder; use crate::search::SearchIndex; @@ -553,6 +553,17 @@ impl ApplicationManager { .await?; let receiver = self.command_broadcaster.subscribe(); + + let clipboard_permissions = plugin.permissions + .clipboard + .into_iter() + .map(|permission| match permission { + DbPluginClipboardPermissions::Read => PluginClipboardPermissions::Read, + DbPluginClipboardPermissions::Write => PluginClipboardPermissions::Write, + DbPluginClipboardPermissions::Clear => PluginClipboardPermissions::Clear, + }) + .collect(); + let data = PluginRuntimeData { id: plugin_id, uuid: plugin.uuid, @@ -566,7 +577,8 @@ impl ApplicationManager { fs_read_access: plugin.permissions.fs_read_access, fs_write_access: plugin.permissions.fs_write_access, run_subprocess: plugin.permissions.run_subprocess, - system: plugin.permissions.system + system: plugin.permissions.system, + clipboard: clipboard_permissions, }, command_receiver: receiver, db_repository: self.db_repository.clone(), diff --git a/tools b/tools index d85ac74..2370948 160000 --- a/tools +++ b/tools @@ -1 +1 @@ -Subproject commit d85ac747727320956a20ee5d50505ad7fff61db0 +Subproject commit 23709487f8e98709fe3f5d03cb2a143a62026dca