mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
* refactor: core plugin permissions are now prefixed core:, closes #10359 * code review * expand reserved plugin names * fix * add core:default permission set * fix permission usage --------- Co-authored-by: Tillmann <28728469+tweidinger@users.noreply.github.com>
949 lines
30 KiB
Rust
949 lines
30 KiB
Rust
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
use crate::Result;
|
|
|
|
use serde_json::{Map, Value};
|
|
use tauri_utils::acl::{
|
|
capability::{Capability, PermissionEntry},
|
|
Scopes, Value as AclValue,
|
|
};
|
|
|
|
use std::{
|
|
collections::{BTreeMap, HashSet},
|
|
fs,
|
|
path::Path,
|
|
};
|
|
|
|
pub fn migrate(tauri_dir: &Path) -> Result<MigratedConfig> {
|
|
if let Ok((mut config, config_path)) =
|
|
tauri_utils_v1::config::parse::parse_value(tauri_dir.join("tauri.conf.json"))
|
|
{
|
|
let migrated = migrate_config(&mut config)?;
|
|
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
|
|
|
|
let mut permissions: Vec<PermissionEntry> = vec!["core:default"]
|
|
.into_iter()
|
|
.map(|p| PermissionEntry::PermissionRef(p.to_string().try_into().unwrap()))
|
|
.collect();
|
|
permissions.extend(migrated.permissions.clone());
|
|
|
|
let capabilities_path = config_path.parent().unwrap().join("capabilities");
|
|
fs::create_dir_all(&capabilities_path)?;
|
|
fs::write(
|
|
capabilities_path.join("migrated.json"),
|
|
serde_json::to_string_pretty(&Capability {
|
|
identifier: "migrated".to_string(),
|
|
description: "permissions that were migrated from v1".into(),
|
|
local: true,
|
|
remote: None,
|
|
windows: vec!["main".into()],
|
|
webviews: vec![],
|
|
permissions,
|
|
platforms: None,
|
|
})?,
|
|
)?;
|
|
|
|
return Ok(migrated);
|
|
}
|
|
|
|
Ok(Default::default())
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct MigratedConfig {
|
|
pub permissions: Vec<PermissionEntry>,
|
|
pub plugins: HashSet<String>,
|
|
}
|
|
|
|
fn migrate_config(config: &mut Value) -> Result<MigratedConfig> {
|
|
let mut migrated = MigratedConfig {
|
|
permissions: Vec::new(),
|
|
plugins: HashSet::new(),
|
|
};
|
|
|
|
if let Some(config) = config.as_object_mut() {
|
|
process_package_metadata(config);
|
|
process_build(config);
|
|
|
|
let mut plugins = config
|
|
.entry("plugins")
|
|
.or_insert_with(|| Value::Object(Default::default()))
|
|
.as_object_mut()
|
|
.unwrap()
|
|
.clone();
|
|
|
|
if let Some(tauri_config) = config.get_mut("tauri").and_then(|c| c.as_object_mut()) {
|
|
// allowlist
|
|
if let Some(allowlist) = tauri_config.remove("allowlist") {
|
|
let allowlist = process_allowlist(tauri_config, allowlist)?;
|
|
let permissions = allowlist_to_permissions(allowlist);
|
|
migrated.plugins = plugins_from_permissions(&permissions);
|
|
migrated.permissions = permissions;
|
|
}
|
|
|
|
// security
|
|
if let Some(security) = tauri_config
|
|
.get_mut("security")
|
|
.and_then(|c| c.as_object_mut())
|
|
{
|
|
process_security(security)?;
|
|
}
|
|
|
|
// tauri > pattern
|
|
if let Some(pattern) = tauri_config.remove("pattern") {
|
|
tauri_config
|
|
.entry("security")
|
|
.or_insert_with(|| Value::Object(Default::default()))
|
|
.as_object_mut()
|
|
.map(|s| s.insert("pattern".into(), pattern));
|
|
}
|
|
|
|
// system tray
|
|
if let Some(tray) = tauri_config.remove("systemTray") {
|
|
tauri_config.insert("trayIcon".into(), tray);
|
|
}
|
|
|
|
// cli
|
|
if let Some(cli) = tauri_config.remove("cli") {
|
|
process_cli(&mut plugins, cli)?;
|
|
}
|
|
|
|
// cli
|
|
process_updater(tauri_config, &mut plugins)?;
|
|
}
|
|
|
|
config.insert("plugins".into(), plugins.into());
|
|
|
|
process_bundle(config);
|
|
|
|
if let Some(tauri_config) = config.remove("tauri") {
|
|
config.insert("app".into(), tauri_config);
|
|
}
|
|
}
|
|
|
|
Ok(migrated)
|
|
}
|
|
|
|
fn process_package_metadata(config: &mut Map<String, Value>) {
|
|
if let Some(mut package_config) = config.remove("package") {
|
|
if let Some(package_config) = package_config.as_object_mut() {
|
|
if let Some(product_name) = package_config.remove("productName") {
|
|
config.insert("productName".into(), product_name);
|
|
}
|
|
|
|
if let Some(version) = package_config.remove("version") {
|
|
config.insert("version".into(), version);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(bundle_config) = config
|
|
.get_mut("tauri")
|
|
.and_then(|t| t.get_mut("bundle"))
|
|
.and_then(|b| b.as_object_mut())
|
|
{
|
|
if let Some(identifier) = bundle_config.remove("identifier") {
|
|
config.insert("identifier".into(), identifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process_build(config: &mut Map<String, Value>) {
|
|
if let Some(build_config) = config.get_mut("build").and_then(|b| b.as_object_mut()) {
|
|
if let Some(dist_dir) = build_config.remove("distDir") {
|
|
build_config.insert("frontendDist".into(), dist_dir);
|
|
}
|
|
if let Some(dev_path) = build_config.remove("devPath") {
|
|
let is_url = url::Url::parse(dev_path.as_str().unwrap_or_default()).is_ok();
|
|
if is_url {
|
|
build_config.insert("devUrl".into(), dev_path);
|
|
}
|
|
}
|
|
if let Some(with_global_tauri) = build_config.remove("withGlobalTauri") {
|
|
config
|
|
.get_mut("tauri")
|
|
.and_then(|t| t.as_object_mut())
|
|
.map(|t| t.insert("withGlobalTauri".into(), with_global_tauri));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process_bundle(config: &mut Map<String, Value>) {
|
|
let mut license_file = None;
|
|
|
|
if let Some(mut bundle_config) = config
|
|
.get_mut("tauri")
|
|
.and_then(|b| b.as_object_mut())
|
|
.and_then(|t| t.remove("bundle"))
|
|
{
|
|
if let Some(bundle_config) = bundle_config.as_object_mut() {
|
|
if let Some(deb) = bundle_config.remove("deb") {
|
|
bundle_config
|
|
.entry("linux")
|
|
.or_insert_with(|| Value::Object(Default::default()))
|
|
.as_object_mut()
|
|
.map(|l| l.insert("deb".into(), deb));
|
|
}
|
|
|
|
if let Some(appimage) = bundle_config.remove("appimage") {
|
|
bundle_config
|
|
.entry("linux")
|
|
.or_insert_with(|| Value::Object(Default::default()))
|
|
.as_object_mut()
|
|
.map(|l| l.insert("appimage".into(), appimage));
|
|
}
|
|
|
|
// license file
|
|
if let Some(macos) = bundle_config.get_mut("macOS") {
|
|
if let Some(license) = macos.as_object_mut().unwrap().remove("license") {
|
|
license_file = Some(license);
|
|
}
|
|
}
|
|
if let Some(windows) = bundle_config.get_mut("windows") {
|
|
if let Some(wix) = windows.get_mut("wix") {
|
|
if let Some(license_path) = wix.as_object_mut().unwrap().remove("license") {
|
|
license_file = Some(license_path);
|
|
}
|
|
}
|
|
if let Some(nsis) = windows.get_mut("nsis") {
|
|
if let Some(license_path) = nsis.as_object_mut().unwrap().remove("license") {
|
|
license_file = Some(license_path);
|
|
}
|
|
}
|
|
}
|
|
if let Some(license_file) = license_file {
|
|
bundle_config.insert("licenseFile".into(), license_file);
|
|
}
|
|
|
|
// Migrate updater from targets to update field
|
|
if let Some(targets) = bundle_config.get_mut("targets") {
|
|
let shuold_migrate = if let Some(targets) = targets.as_array_mut() {
|
|
// targets: ["updater", ...]
|
|
if let Some(index) = targets
|
|
.iter()
|
|
.position(|target| *target == serde_json::Value::String("updater".to_owned()))
|
|
{
|
|
targets.remove(index);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
} else if let Some(target) = targets.as_str() {
|
|
// targets: "updater"
|
|
// targets: "all"
|
|
if target == "updater" {
|
|
bundle_config.remove("targets");
|
|
true
|
|
} else {
|
|
target == "all"
|
|
}
|
|
} else {
|
|
false
|
|
};
|
|
if shuold_migrate {
|
|
bundle_config.insert("createUpdaterArtifacts".to_owned(), "v1Compatible".into());
|
|
}
|
|
}
|
|
}
|
|
|
|
config.insert("bundle".into(), bundle_config);
|
|
}
|
|
}
|
|
|
|
fn process_security(security: &mut Map<String, Value>) -> Result<()> {
|
|
// migrate CSP: add `ipc:` to `connect-src`
|
|
if let Some(csp_value) = security.remove("csp") {
|
|
let csp = if csp_value.is_null() {
|
|
csp_value
|
|
} else {
|
|
let mut csp: tauri_utils_v1::config::Csp = serde_json::from_value(csp_value)?;
|
|
match &mut csp {
|
|
tauri_utils_v1::config::Csp::Policy(csp) => {
|
|
if csp.contains("connect-src") {
|
|
*csp = csp.replace("connect-src", "connect-src ipc: http://ipc.localhost");
|
|
} else {
|
|
*csp = format!("{csp}; connect-src ipc: http://ipc.localhost");
|
|
}
|
|
}
|
|
tauri_utils_v1::config::Csp::DirectiveMap(csp) => {
|
|
if let Some(connect_src) = csp.get_mut("connect-src") {
|
|
if !connect_src.contains("ipc: http://ipc.localhost") {
|
|
connect_src.push("ipc: http://ipc.localhost");
|
|
}
|
|
} else {
|
|
csp.insert(
|
|
"connect-src".into(),
|
|
tauri_utils_v1::config::CspDirectiveSources::List(vec![
|
|
"ipc: http://ipc.localhost".to_string()
|
|
]),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
serde_json::to_value(csp)?
|
|
};
|
|
|
|
security.insert("csp".into(), csp);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn process_allowlist(
|
|
tauri_config: &mut Map<String, Value>,
|
|
allowlist: Value,
|
|
) -> Result<tauri_utils_v1::config::AllowlistConfig> {
|
|
let allowlist: tauri_utils_v1::config::AllowlistConfig = serde_json::from_value(allowlist)?;
|
|
|
|
if allowlist.protocol.asset_scope != Default::default() {
|
|
let security = tauri_config
|
|
.entry("security")
|
|
.or_insert_with(|| Value::Object(Default::default()))
|
|
.as_object_mut()
|
|
.unwrap();
|
|
|
|
let mut asset_protocol = Map::new();
|
|
asset_protocol.insert(
|
|
"scope".into(),
|
|
serde_json::to_value(allowlist.protocol.asset_scope.clone())?,
|
|
);
|
|
if allowlist.protocol.asset {
|
|
asset_protocol.insert("enable".into(), true.into());
|
|
}
|
|
security.insert("assetProtocol".into(), asset_protocol.into());
|
|
}
|
|
|
|
Ok(allowlist)
|
|
}
|
|
|
|
fn allowlist_to_permissions(
|
|
allowlist: tauri_utils_v1::config::AllowlistConfig,
|
|
) -> Vec<PermissionEntry> {
|
|
macro_rules! permissions {
|
|
($allowlist: ident, $permissions_list: ident, $object: ident, $field: ident => $associated_permission: expr) => {
|
|
if $allowlist.all || $allowlist.$object.all || $allowlist.$object.$field {
|
|
$permissions_list.push(PermissionEntry::PermissionRef(
|
|
$associated_permission.to_string().try_into().unwrap(),
|
|
));
|
|
}
|
|
};
|
|
}
|
|
|
|
let mut permissions = Vec::new();
|
|
|
|
// fs
|
|
permissions!(allowlist, permissions, fs, read_file => "fs:allow-read-file");
|
|
permissions!(allowlist, permissions, fs, write_file => "fs:allow-write-file");
|
|
permissions!(allowlist, permissions, fs, read_dir => "fs:allow-read-dir");
|
|
permissions!(allowlist, permissions, fs, copy_file => "fs:allow-copy-file");
|
|
permissions!(allowlist, permissions, fs, create_dir => "fs:allow-mkdir");
|
|
permissions!(allowlist, permissions, fs, remove_dir => "fs:allow-remove");
|
|
permissions!(allowlist, permissions, fs, remove_file => "fs:allow-remove");
|
|
permissions!(allowlist, permissions, fs, rename_file => "fs:allow-rename");
|
|
permissions!(allowlist, permissions, fs, exists => "fs:allow-exists");
|
|
let (fs_allowed, fs_denied) = match allowlist.fs.scope {
|
|
tauri_utils_v1::config::FsAllowlistScope::AllowedPaths(paths) => (paths, Vec::new()),
|
|
tauri_utils_v1::config::FsAllowlistScope::Scope { allow, deny, .. } => (allow, deny),
|
|
};
|
|
if !(fs_allowed.is_empty() && fs_denied.is_empty()) {
|
|
let fs_allowed = fs_allowed
|
|
.into_iter()
|
|
.map(|p| AclValue::String(p.to_string_lossy().into()))
|
|
.collect::<Vec<_>>();
|
|
let fs_denied = fs_denied
|
|
.into_iter()
|
|
.map(|p| AclValue::String(p.to_string_lossy().into()))
|
|
.collect::<Vec<_>>();
|
|
permissions.push(PermissionEntry::ExtendedPermission {
|
|
identifier: "fs:scope".to_string().try_into().unwrap(),
|
|
scope: Scopes {
|
|
allow: if fs_allowed.is_empty() {
|
|
None
|
|
} else {
|
|
Some(fs_allowed)
|
|
},
|
|
deny: if fs_denied.is_empty() {
|
|
None
|
|
} else {
|
|
Some(fs_denied)
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// window
|
|
permissions!(allowlist, permissions, window, create => "core:window:allow-create");
|
|
permissions!(allowlist, permissions, window, center => "core:window:allow-center");
|
|
permissions!(allowlist, permissions, window, request_user_attention => "core:window:allow-request-user-attention");
|
|
permissions!(allowlist, permissions, window, set_resizable => "core:window:allow-set-resizable");
|
|
permissions!(allowlist, permissions, window, set_maximizable => "core:window:allow-set-maximizable");
|
|
permissions!(allowlist, permissions, window, set_minimizable => "core:window:allow-set-minimizable");
|
|
permissions!(allowlist, permissions, window, set_closable => "core:window:allow-set-closable");
|
|
permissions!(allowlist, permissions, window, set_title => "core:window:allow-set-title");
|
|
permissions!(allowlist, permissions, window, maximize => "core:window:allow-maximize");
|
|
permissions!(allowlist, permissions, window, unmaximize => "core:window:allow-unmaximize");
|
|
permissions!(allowlist, permissions, window, minimize => "core:window:allow-minimize");
|
|
permissions!(allowlist, permissions, window, unminimize => "core:window:allow-unminimize");
|
|
permissions!(allowlist, permissions, window, show => "core:window:allow-show");
|
|
permissions!(allowlist, permissions, window, hide => "core:window:allow-hide");
|
|
permissions!(allowlist, permissions, window, close => "core:window:allow-close");
|
|
permissions!(allowlist, permissions, window, set_decorations => "core:window:allow-set-decorations");
|
|
permissions!(allowlist, permissions, window, set_always_on_top => "core:window:allow-set-always-on-top");
|
|
permissions!(allowlist, permissions, window, set_content_protected => "core:window:allow-set-content-protected");
|
|
permissions!(allowlist, permissions, window, set_size => "core:window:allow-set-size");
|
|
permissions!(allowlist, permissions, window, set_min_size => "core:window:allow-set-min-size");
|
|
permissions!(allowlist, permissions, window, set_max_size => "core:window:allow-set-max-size");
|
|
permissions!(allowlist, permissions, window, set_position => "core:window:allow-set-position");
|
|
permissions!(allowlist, permissions, window, set_fullscreen => "core:window:allow-set-fullscreen");
|
|
permissions!(allowlist, permissions, window, set_focus => "core:window:allow-set-focus");
|
|
permissions!(allowlist, permissions, window, set_icon => "core:window:allow-set-icon");
|
|
permissions!(allowlist, permissions, window, set_skip_taskbar => "core:window:allow-set-skip-taskbar");
|
|
permissions!(allowlist, permissions, window, set_cursor_grab => "core:window:allow-set-cursor-grab");
|
|
permissions!(allowlist, permissions, window, set_cursor_visible => "core:window:allow-set-cursor-visible");
|
|
permissions!(allowlist, permissions, window, set_cursor_icon => "core:window:allow-set-cursor-icon");
|
|
permissions!(allowlist, permissions, window, set_cursor_position => "core:window:allow-set-cursor-position");
|
|
permissions!(allowlist, permissions, window, set_ignore_cursor_events => "core:window:allow-set-ignore-cursor-events");
|
|
permissions!(allowlist, permissions, window, start_dragging => "core:window:allow-start-dragging");
|
|
permissions!(allowlist, permissions, window, print => "core:webview:allow-print");
|
|
|
|
// shell
|
|
if allowlist.shell.scope.0.is_empty() {
|
|
permissions!(allowlist, permissions, shell, execute => "shell:allow-execute");
|
|
permissions!(allowlist, permissions, shell, sidecar => "shell:allow-execute");
|
|
} else {
|
|
let allowed = allowlist
|
|
.shell
|
|
.scope
|
|
.0
|
|
.into_iter()
|
|
.map(|p| serde_json::to_value(p).unwrap().into())
|
|
.collect::<Vec<_>>();
|
|
|
|
permissions.push(PermissionEntry::ExtendedPermission {
|
|
identifier: "shell:allow-execute".to_string().try_into().unwrap(),
|
|
scope: Scopes {
|
|
allow: Some(allowed),
|
|
deny: None,
|
|
},
|
|
});
|
|
}
|
|
|
|
if allowlist.all
|
|
|| allowlist.shell.all
|
|
|| !matches!(
|
|
allowlist.shell.open,
|
|
tauri_utils_v1::config::ShellAllowlistOpen::Flag(false)
|
|
)
|
|
{
|
|
permissions.push(PermissionEntry::PermissionRef(
|
|
"shell:allow-open".to_string().try_into().unwrap(),
|
|
));
|
|
}
|
|
// dialog
|
|
permissions!(allowlist, permissions, dialog, open => "dialog:allow-open");
|
|
permissions!(allowlist, permissions, dialog, save => "dialog:allow-save");
|
|
permissions!(allowlist, permissions, dialog, message => "dialog:allow-message");
|
|
permissions!(allowlist, permissions, dialog, ask => "dialog:allow-ask");
|
|
permissions!(allowlist, permissions, dialog, confirm => "dialog:allow-confirm");
|
|
|
|
// http
|
|
if allowlist.http.scope.0.is_empty() {
|
|
permissions!(allowlist, permissions, http, request => "http:default");
|
|
} else {
|
|
let allowed = allowlist
|
|
.http
|
|
.scope
|
|
.0
|
|
.into_iter()
|
|
.map(|p| {
|
|
let mut map = BTreeMap::new();
|
|
map.insert("url".to_string(), AclValue::String(p.to_string()));
|
|
AclValue::Map(map)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
permissions.push(PermissionEntry::ExtendedPermission {
|
|
identifier: "http:default".to_string().try_into().unwrap(),
|
|
scope: Scopes {
|
|
allow: Some(allowed),
|
|
deny: None,
|
|
},
|
|
});
|
|
}
|
|
|
|
// notification
|
|
permissions!(allowlist, permissions, notification, all => "notification:default");
|
|
// global-shortcut
|
|
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-is-registered");
|
|
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-register");
|
|
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-register-all");
|
|
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-unregister");
|
|
permissions!(allowlist, permissions, global_shortcut, all => "global-shortcut:allow-unregister-all");
|
|
// os
|
|
permissions!(allowlist, permissions, os, all => "os:allow-platform");
|
|
permissions!(allowlist, permissions, os, all => "os:allow-version");
|
|
permissions!(allowlist, permissions, os, all => "os:allow-os-type");
|
|
permissions!(allowlist, permissions, os, all => "os:allow-family");
|
|
permissions!(allowlist, permissions, os, all => "os:allow-arch");
|
|
permissions!(allowlist, permissions, os, all => "os:allow-exe-extension");
|
|
permissions!(allowlist, permissions, os, all => "os:allow-locale");
|
|
permissions!(allowlist, permissions, os, all => "os:allow-hostname");
|
|
// process
|
|
permissions!(allowlist, permissions, process, relaunch => "process:allow-restart");
|
|
permissions!(allowlist, permissions, process, exit => "process:allow-exit");
|
|
// clipboard
|
|
permissions!(allowlist, permissions, clipboard, read_text => "clipboard-manager:allow-read-text");
|
|
permissions!(allowlist, permissions, clipboard, write_text => "clipboard-manager:allow-write-text");
|
|
// app
|
|
permissions!(allowlist, permissions, app, show => "core:app:allow-app-show");
|
|
permissions!(allowlist, permissions, app, hide => "core:app:allow-app-hide");
|
|
|
|
permissions
|
|
}
|
|
|
|
fn process_cli(plugins: &mut Map<String, Value>, cli: Value) -> Result<()> {
|
|
if let Some(cli) = cli.as_object() {
|
|
plugins.insert("cli".into(), serde_json::to_value(cli)?);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn process_updater(
|
|
tauri_config: &mut Map<String, Value>,
|
|
plugins: &mut Map<String, Value>,
|
|
) -> Result<()> {
|
|
if let Some(mut updater) = tauri_config.remove("updater") {
|
|
if let Some(updater) = updater.as_object_mut() {
|
|
updater.remove("dialog");
|
|
|
|
// we only migrate the updater config if it's active
|
|
// since we now assume it's always active if the config object is set
|
|
// we also migrate if pubkey is set so we do not lose that information on the migration
|
|
// in this case, the user need to deal with the updater being inactive on their own
|
|
if updater
|
|
.remove("active")
|
|
.and_then(|a| a.as_bool())
|
|
.unwrap_or_default()
|
|
|| updater.get("pubkey").is_some()
|
|
{
|
|
plugins.insert("updater".into(), serde_json::to_value(updater)?);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const KNOWN_PLUGINS: &[&str] = &[
|
|
"fs",
|
|
"shell",
|
|
"dialog",
|
|
"http",
|
|
"notification",
|
|
"global-shortcut",
|
|
"os",
|
|
"process",
|
|
"clipboard-manager",
|
|
];
|
|
|
|
fn plugins_from_permissions(permissions: &Vec<PermissionEntry>) -> HashSet<String> {
|
|
let mut plugins = HashSet::new();
|
|
|
|
for permission in permissions {
|
|
let permission = permission.identifier().get();
|
|
for plugin in KNOWN_PLUGINS {
|
|
if permission.starts_with(plugin) {
|
|
plugins.insert(plugin.to_string());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
plugins
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
fn migrate(original: &serde_json::Value) -> serde_json::Value {
|
|
let mut migrated = original.clone();
|
|
super::migrate_config(&mut migrated).expect("failed to migrate config");
|
|
|
|
if let Err(e) = serde_json::from_value::<tauri_utils::config::Config>(migrated.clone()) {
|
|
panic!("migrated config is not valid: {e}");
|
|
}
|
|
|
|
migrated
|
|
}
|
|
|
|
#[test]
|
|
fn migrate_full() {
|
|
let original = serde_json::json!({
|
|
"build": {
|
|
"distDir": "../dist",
|
|
"devPath": "http://localhost:1240",
|
|
"withGlobalTauri": true
|
|
},
|
|
"package": {
|
|
"productName": "Tauri app",
|
|
"version": "0.0.0"
|
|
},
|
|
"tauri": {
|
|
"bundle": {
|
|
"identifier": "com.tauri.test",
|
|
"deb": {
|
|
"depends": ["dep1"]
|
|
},
|
|
"appimage": {
|
|
"bundleMediaFramework": true
|
|
},
|
|
"macOS": {
|
|
"license": "license-file.txt"
|
|
},
|
|
"windows": {
|
|
"wix": {
|
|
"license": "license-file.txt"
|
|
},
|
|
"nsis": {
|
|
"license": "license-file.txt"
|
|
},
|
|
},
|
|
},
|
|
"cli": {
|
|
"description": "Tauri TEST"
|
|
},
|
|
"updater": {
|
|
"active": true,
|
|
"dialog": false,
|
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
|
|
"endpoints": [
|
|
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
|
|
],
|
|
"windows": {
|
|
"installerArgs": ["arg1"],
|
|
"installMode": "passive"
|
|
}
|
|
},
|
|
"allowlist": {
|
|
"all": true,
|
|
"fs": {
|
|
"scope": {
|
|
"allow": ["$APPDATA/db/**", "$DOWNLOAD/**", "$RESOURCE/**"],
|
|
"deny": ["$APPDATA/db/*.stronghold"]
|
|
}
|
|
},
|
|
"shell": {
|
|
"open": true,
|
|
"scope": [
|
|
{
|
|
"name": "sh",
|
|
"cmd": "sh",
|
|
"args": ["-c", { "validator": "\\S+" }],
|
|
"sidecar": false
|
|
},
|
|
{
|
|
"name": "cmd",
|
|
"cmd": "cmd",
|
|
"args": ["/C", { "validator": "\\S+" }],
|
|
"sidecar": false
|
|
}
|
|
]
|
|
},
|
|
"protocol": {
|
|
"asset": true,
|
|
"assetScope": {
|
|
"allow": ["$APPDATA/db/**", "$RESOURCE/**"],
|
|
"deny": ["$APPDATA/db/*.stronghold"]
|
|
}
|
|
},
|
|
"http": {
|
|
"scope": ["http://localhost:3003/"]
|
|
}
|
|
},
|
|
"pattern": { "use": "brownfield" },
|
|
"security": {
|
|
"csp": "default-src 'self' tauri:"
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
|
|
// plugins > updater
|
|
assert_eq!(
|
|
migrated["plugins"]["updater"]["endpoints"],
|
|
original["tauri"]["updater"]["endpoints"]
|
|
);
|
|
assert_eq!(
|
|
migrated["plugins"]["updater"]["pubkey"],
|
|
original["tauri"]["updater"]["pubkey"]
|
|
);
|
|
assert_eq!(
|
|
migrated["plugins"]["updater"]["windows"]["installMode"],
|
|
original["tauri"]["updater"]["windows"]["installMode"]
|
|
);
|
|
assert_eq!(
|
|
migrated["plugins"]["updater"]["windows"]["installerArgs"],
|
|
original["tauri"]["updater"]["windows"]["installerArgs"]
|
|
);
|
|
|
|
// cli
|
|
assert_eq!(migrated["plugins"]["cli"], original["tauri"]["cli"]);
|
|
|
|
// asset scope
|
|
assert_eq!(
|
|
migrated["app"]["security"]["assetProtocol"]["enable"],
|
|
original["tauri"]["allowlist"]["protocol"]["asset"]
|
|
);
|
|
assert_eq!(
|
|
migrated["app"]["security"]["assetProtocol"]["scope"]["allow"],
|
|
original["tauri"]["allowlist"]["protocol"]["assetScope"]["allow"]
|
|
);
|
|
assert_eq!(
|
|
migrated["app"]["security"]["assetProtocol"]["scope"]["deny"],
|
|
original["tauri"]["allowlist"]["protocol"]["assetScope"]["deny"]
|
|
);
|
|
|
|
// security CSP
|
|
assert_eq!(
|
|
migrated["app"]["security"]["csp"],
|
|
format!(
|
|
"{}; connect-src ipc: http://ipc.localhost",
|
|
original["tauri"]["security"]["csp"].as_str().unwrap()
|
|
)
|
|
);
|
|
|
|
// security pattern
|
|
assert_eq!(
|
|
migrated["app"]["security"]["pattern"],
|
|
original["tauri"]["pattern"]
|
|
);
|
|
|
|
// license files
|
|
assert_eq!(
|
|
migrated["bundle"]["licenseFile"],
|
|
original["tauri"]["bundle"]["macOS"]["license"]
|
|
);
|
|
assert_eq!(
|
|
migrated["bundle"]["licenseFile"],
|
|
original["tauri"]["bundle"]["windows"]["wix"]["license"]
|
|
);
|
|
assert_eq!(
|
|
migrated["bundle"]["licenseFile"],
|
|
original["tauri"]["bundle"]["windows"]["nsis"]["license"]
|
|
);
|
|
|
|
// bundle appimage and deb
|
|
assert_eq!(
|
|
migrated["bundle"]["linux"]["deb"],
|
|
original["tauri"]["bundle"]["deb"]
|
|
);
|
|
assert_eq!(
|
|
migrated["bundle"]["linux"]["appimage"],
|
|
original["tauri"]["bundle"]["appimage"]
|
|
);
|
|
|
|
// app information
|
|
assert_eq!(migrated["productName"], original["package"]["productName"]);
|
|
assert_eq!(migrated["version"], original["package"]["version"]);
|
|
assert_eq!(
|
|
migrated["identifier"],
|
|
original["tauri"]["bundle"]["identifier"]
|
|
);
|
|
|
|
// build object
|
|
assert_eq!(
|
|
migrated["build"]["frontendDist"],
|
|
original["build"]["distDir"]
|
|
);
|
|
assert_eq!(migrated["build"]["devUrl"], original["build"]["devPath"]);
|
|
assert_eq!(
|
|
migrated["app"]["withGlobalTauri"],
|
|
original["build"]["withGlobalTauri"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn skips_migrating_updater() {
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"updater": {
|
|
"active": false
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
assert_eq!(migrated["plugins"]["updater"], serde_json::Value::Null);
|
|
}
|
|
|
|
#[test]
|
|
fn migrate_updater_pubkey() {
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"updater": {
|
|
"active": false,
|
|
"pubkey": "somekey"
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
assert_eq!(
|
|
migrated["plugins"]["updater"]["pubkey"],
|
|
original["tauri"]["updater"]["pubkey"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn migrate_updater_target() {
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"bundle": {
|
|
"targets": ["nsis", "updater"]
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
|
|
assert_eq!(
|
|
migrated["bundle"]["targets"].as_array(),
|
|
Some(&vec!["nsis".into()])
|
|
);
|
|
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"bundle": {
|
|
"targets": "all"
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
|
|
assert_eq!(migrated["bundle"]["targets"], "all");
|
|
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"bundle": {
|
|
"targets": "updater"
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
|
|
assert_eq!(migrated["bundle"].get("targets"), None);
|
|
}
|
|
|
|
#[test]
|
|
fn migrate_csp_object() {
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"security": {
|
|
"csp": {
|
|
"default-src": ["self", "tauri:"]
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
|
|
assert_eq!(
|
|
migrated["app"]["security"]["csp"]["default-src"],
|
|
original["tauri"]["security"]["csp"]["default-src"]
|
|
);
|
|
assert!(migrated["app"]["security"]["csp"]["connect-src"]
|
|
.as_array()
|
|
.expect("connect-src isn't an array")
|
|
.contains(&"ipc: http://ipc.localhost".into()));
|
|
}
|
|
|
|
#[test]
|
|
fn migrate_csp_existing_connect_src_string() {
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"security": {
|
|
"csp": {
|
|
"default-src": ["self", "tauri:"],
|
|
"connect-src": "self"
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
|
|
assert_eq!(
|
|
migrated["app"]["security"]["csp"]["default-src"],
|
|
original["tauri"]["security"]["csp"]["default-src"]
|
|
);
|
|
assert_eq!(
|
|
migrated["app"]["security"]["csp"]["connect-src"]
|
|
.as_str()
|
|
.expect("connect-src isn't a string"),
|
|
format!(
|
|
"{} ipc: http://ipc.localhost",
|
|
original["tauri"]["security"]["csp"]["connect-src"]
|
|
.as_str()
|
|
.unwrap()
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn migrate_csp_existing_connect_src_array() {
|
|
let original = serde_json::json!({
|
|
"tauri": {
|
|
"security": {
|
|
"csp": {
|
|
"default-src": ["self", "tauri:"],
|
|
"connect-src": ["self", "asset:"]
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
|
|
assert_eq!(
|
|
migrated["app"]["security"]["csp"]["default-src"],
|
|
original["tauri"]["security"]["csp"]["default-src"]
|
|
);
|
|
|
|
let migrated_connect_src = migrated["app"]["security"]["csp"]["connect-src"]
|
|
.as_array()
|
|
.expect("connect-src isn't an array");
|
|
let original_connect_src = original["tauri"]["security"]["csp"]["connect-src"]
|
|
.as_array()
|
|
.unwrap();
|
|
assert!(
|
|
migrated_connect_src
|
|
.iter()
|
|
.zip(original_connect_src.iter())
|
|
.all(|(a, b)| a == b),
|
|
"connect-src migration failed"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn migrate_invalid_url_dev_path() {
|
|
let original = serde_json::json!({
|
|
"build": {
|
|
"devPath": "../src",
|
|
"distDir": "../src"
|
|
}
|
|
});
|
|
|
|
let migrated = migrate(&original);
|
|
|
|
assert!(migrated["build"].get("devUrl").is_none());
|
|
assert_eq!(
|
|
migrated["build"]["distDir"],
|
|
original["build"]["frontendDist"]
|
|
);
|
|
}
|
|
}
|