[WIP] Encryption for secure values (#183)

This commit is contained in:
Gregory Schier
2025-04-15 07:18:26 -07:00
committed by GitHub
parent e114a85c39
commit 2e55a1bd6d
208 changed files with 4063 additions and 28698 deletions

View File

@@ -5,6 +5,12 @@ use tokio::sync::mpsc::error::SendError;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
CryptoErr(#[from] yaak_crypto::error::Error),
#[error(transparent)]
TemplateErr(#[from] yaak_templates::error::Error),
#[error("IO error: {0}")]
IoErr(#[from] io::Error),

View File

@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tauri::{Runtime, WebviewWindow};
use ts_rs::TS;
use yaak_common::window::WorkspaceWindowTrait;
use yaak_models::models::{
Environment, Folder, GrpcRequest, HttpRequest, HttpResponse, WebsocketRequest, Workspace,
};
@@ -15,7 +15,7 @@ pub struct InternalEvent {
pub plugin_ref_id: String,
pub plugin_name: String,
pub reply_id: Option<String>,
pub window_context: WindowContext,
pub window_context: PluginWindowContext,
pub payload: InternalEventPayload,
}
@@ -29,22 +29,32 @@ pub(crate) struct InternalEventRawPayload {
pub plugin_ref_id: String,
pub plugin_name: String,
pub reply_id: Option<String>,
pub window_context: WindowContext,
pub window_context: PluginWindowContext,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case", tag = "type")]
#[ts(export, export_to = "gen_events.ts")]
pub enum WindowContext {
pub enum PluginWindowContext {
None,
Label { label: String },
Label {
label: String,
workspace_id: Option<String>,
},
}
impl WindowContext {
pub fn from_window<R: Runtime>(window: &WebviewWindow<R>) -> Self {
impl PluginWindowContext {
pub fn new<R: Runtime>(window: &WebviewWindow<R>) -> Self {
Self::Label {
label: window.label().to_string(),
workspace_id: window.workspace_id(),
}
}
pub fn new_no_workspace<R: Runtime>(window: &WebviewWindow<R>) -> Self {
Self::Label {
label: window.label().to_string(),
workspace_id: None,
}
}
}
@@ -497,7 +507,15 @@ pub struct TemplateFunction {
/// tags when changing the `name` property
#[ts(optional)]
pub aliases: Option<Vec<String>>,
pub args: Vec<FormInput>,
pub args: Vec<TemplateFunctionArg>,
}
/// Similar to FormInput, but contains
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case", untagged)]
#[ts(export, export_to = "gen_events.ts")]
pub enum TemplateFunctionArg {
FormInput(FormInput),
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]

View File

@@ -7,10 +7,11 @@ use tauri::{Manager, RunEvent, Runtime, State};
pub mod error;
pub mod events;
pub mod manager;
pub mod plugin_handle;
pub mod template_callback;
pub mod native_template_functions;
mod nodejs;
pub mod plugin_handle;
mod server_ws;
pub mod template_callback;
mod util;
pub fn init<R: Runtime>() -> TauriPlugin<R> {
@@ -18,7 +19,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.setup(|app_handle, _| {
let manager = PluginManager::new(app_handle.clone());
app_handle.manage(manager.clone());
Ok(())
})
.on_event(|app, e| match e {

View File

@@ -9,7 +9,7 @@ use crate::events::{
EmptyPayload, FilterRequest, FilterResponse, GetHttpAuthenticationConfigRequest,
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, ImportRequest, ImportResponse,
InternalEvent, InternalEventPayload, JsonPrimitive, RenderPurpose, WindowContext,
InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose,
};
use crate::nodejs::start_nodejs_plugin_runtime;
use crate::plugin_handle::PluginHandle;
@@ -30,6 +30,7 @@ use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::generate_id;
use yaak_templates::error::Error::RenderError;
use yaak_templates::error::Result as TemplateResult;
use crate::native_template_functions::template_function_secure;
#[derive(Clone)]
pub struct PluginManager {
@@ -100,7 +101,7 @@ impl PluginManager {
Ok(_) => {
info!("Plugin runtime client connected!");
plugin_manager
.initialize_all_plugins(&app_handle, &WindowContext::None)
.initialize_all_plugins(&app_handle, &PluginWindowContext::None)
.await
.expect("Failed to reload plugins");
}
@@ -172,14 +173,14 @@ impl PluginManager {
[bundled_plugin_dirs, installed_plugin_dirs].concat()
}
pub async fn uninstall(&self, window_context: &WindowContext, dir: &str) -> Result<()> {
pub async fn uninstall(&self, window_context: &PluginWindowContext, dir: &str) -> Result<()> {
let plugin = self.get_plugin_by_dir(dir).await.ok_or(PluginNotFoundErr(dir.to_string()))?;
self.remove_plugin(window_context, &plugin).await
}
async fn remove_plugin(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
plugin: &PluginHandle,
) -> Result<()> {
// Terminate the plugin
@@ -197,7 +198,7 @@ impl PluginManager {
pub async fn add_plugin_by_dir(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
dir: &str,
watch: bool,
) -> Result<()> {
@@ -240,7 +241,7 @@ impl PluginManager {
pub async fn initialize_all_plugins<R: Runtime>(
&self,
app_handle: &AppHandle<R>,
window_context: &WindowContext,
window_context: &PluginWindowContext,
) -> Result<()> {
let start = Instant::now();
let candidates = self.list_plugin_dirs(app_handle).await;
@@ -325,7 +326,7 @@ impl PluginManager {
async fn send_to_plugin_and_wait(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
plugin: &PluginHandle,
payload: &InternalEventPayload,
) -> Result<InternalEvent> {
@@ -336,7 +337,7 @@ impl PluginManager {
async fn send_and_wait(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
payload: &InternalEventPayload,
) -> Result<Vec<InternalEvent>> {
let plugins = { self.plugins.lock().await.clone() };
@@ -345,7 +346,7 @@ impl PluginManager {
async fn send_to_plugins_and_wait(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
payload: &InternalEventPayload,
plugins: Vec<PluginHandle>,
) -> Result<Vec<InternalEvent>> {
@@ -408,7 +409,7 @@ impl PluginManager {
) -> Result<Vec<GetHttpRequestActionsResponse>> {
let reply_events = self
.send_and_wait(
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
&InternalEventPayload::GetHttpRequestActionsRequest(EmptyPayload {}),
)
.await?;
@@ -427,25 +428,31 @@ impl PluginManager {
&self,
window: &WebviewWindow<R>,
) -> Result<Vec<GetTemplateFunctionsResponse>> {
self.get_template_functions_with_context(&WindowContext::from_window(window)).await
self.get_template_functions_with_context(&PluginWindowContext::new(&window)).await
}
pub async fn get_template_functions_with_context(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
) -> Result<Vec<GetTemplateFunctionsResponse>> {
let reply_events = self
.send_and_wait(window_context, &InternalEventPayload::GetTemplateFunctionsRequest)
.await?;
let mut all_actions = Vec::new();
let mut result = Vec::new();
for event in reply_events {
if let InternalEventPayload::GetTemplateFunctionsResponse(resp) = event.payload {
all_actions.push(resp.clone());
result.push(resp.clone());
}
}
Ok(all_actions)
// Add Rust-based functions
result.push(GetTemplateFunctionsResponse {
plugin_ref_id: "__NATIVE__".to_string(), // Meh
functions: vec![template_function_secure()],
});
Ok(result)
}
pub async fn call_http_request_action<R: Runtime>(
@@ -457,7 +464,7 @@ impl PluginManager {
let plugin =
self.get_plugin_by_ref_id(ref_id.as_str()).await.ok_or(PluginNotFoundErr(ref_id))?;
let event = plugin.build_event_to_send(
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
&InternalEventPayload::CallHttpRequestActionRequest(req),
None,
);
@@ -469,7 +476,7 @@ impl PluginManager {
&self,
window: &WebviewWindow<R>,
) -> Result<Vec<(PluginHandle, GetHttpAuthenticationSummaryResponse)>> {
let window_context = WindowContext::from_window(window);
let window_context = PluginWindowContext::new(window);
let reply_events = self
.send_and_wait(
&window_context,
@@ -508,7 +515,7 @@ impl PluginManager {
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
let event = self
.send_to_plugin_and_wait(
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
&plugin,
&InternalEventPayload::GetHttpAuthenticationConfigRequest(
GetHttpAuthenticationConfigRequest { values, context_id },
@@ -540,7 +547,7 @@ impl PluginManager {
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
self.send_to_plugin_and_wait(
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
&plugin,
&InternalEventPayload::CallHttpAuthenticationActionRequest(
CallHttpAuthenticationActionRequest {
@@ -581,7 +588,7 @@ impl PluginManager {
let event = self
.send_to_plugin_and_wait(
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
&plugin,
&InternalEventPayload::CallHttpAuthenticationRequest(req),
)
@@ -597,7 +604,7 @@ impl PluginManager {
pub async fn call_template_function(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
fn_name: &str,
args: HashMap<String, String>,
purpose: RenderPurpose,
@@ -636,7 +643,7 @@ impl PluginManager {
) -> Result<ImportResponse> {
let reply_events = self
.send_and_wait(
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
&InternalEventPayload::ImportRequest(ImportRequest {
content: content.to_string(),
}),
@@ -675,7 +682,7 @@ impl PluginManager {
let event = self
.send_to_plugin_and_wait(
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
&plugin,
&InternalEventPayload::FilterRequest(FilterRequest {
filter: filter.to_string(),

View File

@@ -0,0 +1,164 @@
use crate::events::{
FormInput, FormInputBase, FormInputText, PluginWindowContext, RenderPurpose, TemplateFunction,
TemplateFunctionArg,
};
use crate::template_callback::PluginTemplateCallback;
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use std::collections::HashMap;
use tauri::{AppHandle, Runtime};
use yaak_crypto::manager::EncryptionManagerExt;
use yaak_templates::error::Error::RenderError;
use yaak_templates::error::Result;
use yaak_templates::{FnArg, Parser, Token, Tokens, Val, transform_args};
pub(crate) fn template_function_secure() -> TemplateFunction {
TemplateFunction {
name: "secure".to_string(),
description: Some("Securely store encrypted text".to_string()),
aliases: None,
args: vec![TemplateFunctionArg::FormInput(FormInput::Text(
FormInputText {
multi_line: Some(true),
password: Some(true),
base: FormInputBase {
name: "value".to_string(),
label: Some("Value".to_string()),
..Default::default()
},
..Default::default()
},
))],
}
}
pub fn template_function_secure_run<R: Runtime>(
app_handle: &AppHandle<R>,
args: HashMap<String, String>,
window_context: &PluginWindowContext,
) -> Result<String> {
match window_context.clone() {
PluginWindowContext::Label {
workspace_id: Some(wid),
..
} => {
let value = args.get("value").map(|v| v.to_owned()).unwrap_or_default();
if value.is_empty() {
return Ok("".to_string());
}
let value = match value.strip_prefix("YENC_") {
None => {
return Err(RenderError("Could not decrypt non-encrypted value".to_string()));
}
Some(v) => v,
};
let value = BASE64_STANDARD.decode(&value).unwrap();
let r = match app_handle.crypto().decrypt(&wid, value.as_slice()) {
Ok(r) => Ok(r),
Err(e) => Err(RenderError(e.to_string())),
}?;
let r = String::from_utf8(r).map_err(|e| RenderError(e.to_string()))?;
Ok(r)
}
_ => Err(RenderError("workspace_id missing from window context".to_string())),
}
}
pub fn template_function_secure_transform_arg<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
arg_name: &str,
arg_value: &str,
) -> Result<String> {
if arg_name != "value" {
return Ok(arg_value.to_string());
}
match window_context.clone() {
PluginWindowContext::Label {
workspace_id: Some(wid),
..
} => {
if arg_value.is_empty() {
return Ok("".to_string());
}
if arg_value.starts_with("YENC_") {
// Already encrypted, so do nothing
return Ok(arg_value.to_string());
}
let r = app_handle
.crypto()
.encrypt(&wid, arg_value.as_bytes())
.map_err(|e| RenderError(e.to_string()))?;
let r = BASE64_STANDARD.encode(r);
Ok(format!("YENC_{}", r))
}
_ => Err(RenderError("workspace_id missing from window context".to_string())),
}
}
pub fn decrypt_secure_template_function<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
template: &str,
) -> Result<String> {
let mut parsed = Parser::new(template).parse()?;
let mut new_tokens: Vec<Token> = Vec::new();
for token in parsed.tokens.iter() {
match token {
Token::Tag {
val: Val::Fn { name, args },
} if name == "secure" => {
let mut args_map = HashMap::new();
for a in args {
match a.clone().value {
Val::Str { text } => {
args_map.insert(a.name.to_string(), text);
}
_ => continue,
}
}
new_tokens.push(Token::Raw {
text: template_function_secure_run(app_handle, args_map, window_context)?,
});
}
t => {
new_tokens.push(t.clone());
continue;
}
};
}
parsed.tokens = new_tokens;
Ok(parsed.to_string())
}
pub fn encrypt_secure_template_function<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
template: &str,
) -> Result<String> {
let decrypted = decrypt_secure_template_function(&app_handle, window_context, template)?;
let tokens = Tokens {
tokens: vec![Token::Tag {
val: Val::Fn {
name: "secure".to_string(),
args: vec![FnArg {
name: "value".to_string(),
value: Val::Str { text: decrypted },
}],
},
}],
};
Ok(transform_args(
tokens,
&PluginTemplateCallback::new(app_handle, window_context, RenderPurpose::Preview),
)?
.to_string())
}

View File

@@ -1,5 +1,5 @@
use crate::error::Result;
use crate::events::{BootResponse, InternalEvent, InternalEventPayload, WindowContext};
use crate::events::{BootResponse, InternalEvent, InternalEventPayload, PluginWindowContext};
use crate::util::gen_id;
use log::info;
use std::path::Path;
@@ -37,7 +37,7 @@ impl PluginHandle {
pub fn build_event_to_send(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
payload: &InternalEventPayload,
reply_id: Option<String>,
) -> InternalEvent {
@@ -46,7 +46,7 @@ impl PluginHandle {
pub(crate) fn build_event_to_send_raw(
&self,
window_context: &WindowContext,
window_context: &PluginWindowContext,
payload: &InternalEventPayload,
reply_id: Option<String>,
) -> InternalEvent {
@@ -61,7 +61,7 @@ impl PluginHandle {
}
}
pub async fn terminate(&self, window_context: &WindowContext) -> Result<()> {
pub async fn terminate(&self, window_context: &PluginWindowContext) -> Result<()> {
info!("Terminating plugin {}", self.dir);
let event =
self.build_event_to_send(window_context, &InternalEventPayload::TerminateRequest, None);

View File

@@ -1,40 +1,46 @@
use crate::events::{RenderPurpose, WindowContext};
use crate::events::{PluginWindowContext, RenderPurpose};
use crate::manager::PluginManager;
use crate::native_template_functions::{
template_function_secure_run, template_function_secure_transform_arg,
};
use std::collections::HashMap;
use tauri::{AppHandle, Manager, Runtime};
use yaak_templates::error::Result;
use yaak_templates::TemplateCallback;
#[derive(Clone)]
pub struct PluginTemplateCallback {
plugin_manager: PluginManager,
window_context: WindowContext,
pub struct PluginTemplateCallback<R: Runtime> {
app_handle: AppHandle<R>,
render_purpose: RenderPurpose,
window_context: PluginWindowContext,
}
impl PluginTemplateCallback {
pub fn new<R: Runtime>(
impl<R: Runtime> PluginTemplateCallback<R> {
pub fn new(
app_handle: &AppHandle<R>,
window_context: &WindowContext,
window_context: &PluginWindowContext,
render_purpose: RenderPurpose,
) -> PluginTemplateCallback {
let plugin_manager = &*app_handle.state::<PluginManager>();
) -> PluginTemplateCallback<R> {
PluginTemplateCallback {
plugin_manager: plugin_manager.to_owned(),
window_context: window_context.to_owned(),
render_purpose,
app_handle: app_handle.to_owned(),
window_context: window_context.to_owned(),
}
}
}
impl TemplateCallback for PluginTemplateCallback {
impl<R: Runtime> TemplateCallback for PluginTemplateCallback<R> {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
// The beta named the function `Response` but was changed in stable.
// Keep this here for a while because there's no easy way to migrate
let fn_name = if fn_name == "Response" { "response" } else { fn_name };
let resp = self
.plugin_manager
if fn_name == "secure" {
return template_function_secure_run(&self.app_handle, args, &self.window_context);
}
let plugin_manager = &*self.app_handle.state::<PluginManager>();
let resp = plugin_manager
.call_template_function(
&self.window_context,
fn_name,
@@ -44,4 +50,22 @@ impl TemplateCallback for PluginTemplateCallback {
.await?;
Ok(resp)
}
fn transform_arg(
&self,
fn_name: &str,
arg_name: &str,
arg_value: &str,
) -> Result<String> {
if fn_name == "secure" {
return template_function_secure_transform_arg(
&self.app_handle,
&self.window_context,
arg_name,
arg_value,
);
}
Ok(arg_value.to_string())
}
}