use std::{
fs::create_dir_all,
path::{Path, PathBuf},
};
use tauri_utils::acl::manifest::PermissionFile;
#[path = "src/scope.rs"]
#[allow(dead_code)]
mod scope;
#[derive(schemars::JsonSchema)]
#[serde(untagged)]
#[allow(unused)]
enum FsScopeEntry {
Value(PathBuf),
Object {
path: PathBuf,
},
}
fn _f() {
match scope::EntryRaw::Value(PathBuf::new()) {
scope::EntryRaw::Value(path) => FsScopeEntry::Value(path),
scope::EntryRaw::Object { path } => FsScopeEntry::Object { path },
};
match FsScopeEntry::Value(PathBuf::new()) {
FsScopeEntry::Value(path) => scope::EntryRaw::Value(path),
FsScopeEntry::Object { path } => scope::EntryRaw::Object { path },
};
}
const BASE_DIR_VARS: &[&str] = &[
"AUDIO",
"CACHE",
"CONFIG",
"DATA",
"LOCALDATA",
"DESKTOP",
"DOCUMENT",
"DOWNLOAD",
"EXE",
"FONT",
"HOME",
"PICTURE",
"PUBLIC",
"RUNTIME",
"TEMPLATE",
"VIDEO",
"RESOURCE",
"LOG",
"TEMP",
"APPCONFIG",
"APPDATA",
"APPLOCALDATA",
"APPCACHE",
"APPLOG",
];
struct Command {
name: &'static str,
dependencies: &'static [&'static str],
}
impl Command {
const fn new(name: &'static str) -> Self {
Self {
name,
dependencies: &[],
}
}
const fn dependencies(self, dependencies: &'static [&'static str]) -> Self {
Self {
name: self.name,
dependencies,
}
}
}
const COMMANDS: &[Command] = &[
Command::new("mkdir"),
Command::new("create"),
Command::new("copy_file"),
Command::new("remove"),
Command::new("rename"),
Command::new("truncate"),
Command::new("ftruncate"),
Command::new("write"),
Command::new("write_file").dependencies(&["open", "write"]),
Command::new("write_text_file"),
Command::new("read_dir"),
Command::new("read_file"),
Command::new("read"),
Command::new("open"),
Command::new("read_text_file"),
Command::new("read_text_file_lines").dependencies(&["read_text_file_lines_next"]),
Command::new("read_text_file_lines_next"),
Command::new("seek"),
Command::new("stat"),
Command::new("lstat"),
Command::new("fstat"),
Command::new("exists"),
Command::new("watch"),
Command::new("unwatch"),
Command::new("size"),
Command::new("start_accessing_security_scoped_resource"),
Command::new("stop_accessing_security_scoped_resource"),
];
fn main() {
let autogenerated = Path::new("permissions/autogenerated/");
let base_dirs = &autogenerated.join("base-directories");
if !base_dirs.exists() {
create_dir_all(base_dirs).expect("unable to create autogenerated base directories dir");
}
for base_dir in BASE_DIR_VARS {
let upper = base_dir;
let lower = base_dir.to_lowercase();
let toml = format!(
r###"# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
# Scopes Section
# This section contains scopes, which define file level access
[[permission]]
identifier = "scope-{lower}-recursive"
description = "This scope permits recursive access to the complete `${upper}` folder, including sub directories and files."
[[permission.scope.allow]]
path = "${upper}"
[[permission.scope.allow]]
path = "${upper}/**"
[[permission]]
identifier = "scope-{lower}"
description = "This scope permits access to all files and list content of top level directories in the `${upper}` folder."
[[permission.scope.allow]]
path = "${upper}"
[[permission.scope.allow]]
path = "${upper}/*"
[[permission]]
identifier = "scope-{lower}-index"
description = "This scope permits to list all files and folders in the `${upper}`folder."
[[permission.scope.allow]]
path = "${upper}"
# Sets Section
# This section combines the scope elements with enablement of commands
[[set]]
identifier = "allow-{lower}-read-recursive"
description = "This allows full recursive read access to the complete `${upper}` folder, files and subdirectories."
permissions = [
"read-all",
"scope-{lower}-recursive"
]
[[set]]
identifier = "allow-{lower}-write-recursive"
description = "This allows full recursive write access to the complete `${upper}` folder, files and subdirectories."
permissions = [
"write-all",
"scope-{lower}-recursive"
]
[[set]]
identifier = "allow-{lower}-read"
description = "This allows non-recursive read access to the `${upper}` folder."
permissions = [
"read-all",
"scope-{lower}"
]
[[set]]
identifier = "allow-{lower}-write"
description = "This allows non-recursive write access to the `${upper}` folder."
permissions = [
"write-all",
"scope-{lower}"
]
[[set]]
identifier = "allow-{lower}-meta-recursive"
description = "This allows full recursive read access to metadata of the `${upper}` folder, including file listing and statistics."
permissions = [
"read-meta",
"scope-{lower}-recursive"
]
[[set]]
identifier = "allow-{lower}-meta"
description = "This allows non-recursive read access to metadata of the `${upper}` folder, including file listing and statistics."
permissions = [
"read-meta",
"scope-{lower}-index"
]"###
);
let permission_path = base_dirs.join(format!("{lower}.toml"));
if toml != std::fs::read_to_string(&permission_path).unwrap_or_default() {
std::fs::write(permission_path, toml)
.unwrap_or_else(|e| panic!("unable to autogenerate ${lower}: {e}"));
}
}
tauri_plugin::Builder::new(
&COMMANDS
.iter()
.filter(|c| c.dependencies.is_empty())
.map(|c| c.name)
.collect::<Vec<_>>(),
)
.global_api_script_path("./api-iife.js")
.global_scope_schema(schemars::schema_for!(FsScopeEntry))
.android_path("android")
.build();
validate_command_dependencies(Path::new("permissions"));
let permissions_dir = autogenerated.join("commands");
for Command {
name: command,
dependencies,
} in COMMANDS
{
if dependencies.is_empty() {
continue;
}
let permission_path = permissions_dir.join(format!("{command}.toml"));
let content = std::fs::read_to_string(&permission_path)
.unwrap_or_else(|_| panic!("failed to read {command}.toml"));
let mut permission_file = toml::from_str::<PermissionFile>(&content)
.unwrap_or_else(|_| panic!("failed to deserialize {command}.toml"));
for p in permission_file
.permission
.iter_mut()
.filter(|p| p.identifier.starts_with("allow"))
{
p.commands.allow = std::iter::once(command)
.chain(dependencies.iter())
.map(|c| c.to_string())
.collect();
}
let out = toml::to_string_pretty(&permission_file)
.unwrap_or_else(|_| panic!("failed to serialize {command}.toml"));
let out = format!(
r#"# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
{out}"#
);
if content != out {
std::fs::write(permission_path, out)
.unwrap_or_else(|_| panic!("failed to write {command}.toml"));
}
}
}
fn validate_command_dependencies(permissions_dir: &Path) {
let mut errors = Vec::new();
for entry in std::fs::read_dir(permissions_dir).expect("failed to read permissions dir") {
let path = entry.expect("failed to read permissions dir entry").path();
if path.extension().and_then(|ext| ext.to_str()) != Some("toml") {
continue;
}
println!("cargo:rerun-if-changed={}", path.display());
let content = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
let permission_file = toml::from_str::<PermissionFile>(&content)
.unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display()));
for permission in &permission_file.permission {
let allowed = &permission.commands.allow;
for command in COMMANDS
.iter()
.filter(|c| allowed.iter().any(|a| a == c.name))
{
for dependency in command.dependencies {
if !allowed.iter().any(|a| a == dependency) {
errors.push(format!(
"{}: `{}` allows `{}` but not its dependency `{dependency}`",
path.display(),
permission.identifier,
command.name,
));
}
}
}
}
}
if !errors.is_empty() {
panic!(
"permissions are missing command dependencies:\n{}",
errors.join("\n")
);
}
}