1use std::{
6 collections::{BTreeMap, HashMap},
7 env, fs,
8 path::{Path, PathBuf},
9};
10
11use anyhow::{Context, Result};
12use tauri_utils::{
13 acl::{
14 capability::Capability,
15 manifest::{Manifest, PermissionFile},
16 schema::CAPABILITIES_SCHEMA_FOLDER_PATH,
17 ACL_MANIFESTS_FILE_NAME, APP_ACL_KEY, CAPABILITIES_FILE_NAME,
18 },
19 platform::Target,
20 write_if_changed,
21};
22
23use crate::Attributes;
24
25#[derive(Debug, Default, Clone)]
32pub struct InlinedPlugin {
33 commands: &'static [&'static str],
34 permissions_path_pattern: Option<&'static str>,
35 default: Option<DefaultPermissionRule>,
36}
37
38#[derive(Debug, Clone)]
40pub enum DefaultPermissionRule {
41 AllowAllCommands,
43 Allow(Vec<String>),
48}
49
50impl InlinedPlugin {
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn commands(mut self, commands: &'static [&'static str]) -> Self {
58 self.commands = commands;
59 self
60 }
61
62 pub fn permissions_path_pattern(mut self, pattern: &'static str) -> Self {
68 self.permissions_path_pattern.replace(pattern);
69 self
70 }
71
72 pub fn default_permission(mut self, default: DefaultPermissionRule) -> Self {
76 self.default.replace(default);
77 self
78 }
79}
80
81#[derive(Debug, Default, Clone, Copy)]
88pub struct AppManifest {
89 commands: &'static [&'static str],
90 permissions_path_pattern: Option<&'static str>,
91}
92
93impl AppManifest {
94 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn commands(mut self, commands: &'static [&'static str]) -> Self {
101 self.commands = commands;
102 self
103 }
104
105 pub fn permissions_path_pattern(mut self, pattern: &'static str) -> Self {
111 self.permissions_path_pattern.replace(pattern);
112 self
113 }
114}
115
116fn save_capabilities(capabilities: &BTreeMap<String, Capability>) -> Result<PathBuf> {
118 let dir = Path::new(CAPABILITIES_SCHEMA_FOLDER_PATH);
119 fs::create_dir_all(dir)?;
120
121 let path = dir.join(CAPABILITIES_FILE_NAME);
122 let json = serde_json::to_string(&capabilities)?;
123 write_if_changed(&path, json)?;
124
125 Ok(path)
126}
127
128fn save_acl_manifests(acl_manifests: &BTreeMap<String, Manifest>) -> Result<PathBuf> {
130 let dir = Path::new(CAPABILITIES_SCHEMA_FOLDER_PATH);
131 fs::create_dir_all(dir)?;
132
133 let path = dir.join(ACL_MANIFESTS_FILE_NAME);
134 let json = serde_json::to_string(&acl_manifests)?;
135 write_if_changed(&path, json)?;
136
137 Ok(path)
138}
139
140fn read_plugins_manifests() -> Result<BTreeMap<String, Manifest>> {
142 use tauri_utils::acl;
143
144 let permission_map =
145 acl::build::read_permissions().context("failed to read plugin permissions")?;
146 let mut global_scope_map =
147 acl::build::read_global_scope_schemas().context("failed to read global scope schemas")?;
148
149 let mut manifests = BTreeMap::new();
150
151 for (plugin_name, permission_files) in permission_map {
152 let global_scope_schema = global_scope_map.remove(&plugin_name);
153 let manifest = Manifest::new(permission_files, global_scope_schema);
154 manifests.insert(plugin_name, manifest);
155 }
156
157 Ok(manifests)
158}
159
160struct InlinedPluginsAcl {
161 manifests: BTreeMap<String, Manifest>,
162 permission_files: BTreeMap<String, Vec<PermissionFile>>,
163}
164
165fn inline_plugins(
166 out_dir: &Path,
167 inlined_plugins: HashMap<&'static str, InlinedPlugin>,
168) -> Result<InlinedPluginsAcl> {
169 let mut acl_manifests = BTreeMap::new();
170 let mut permission_files_map = BTreeMap::new();
171
172 for (name, plugin) in inlined_plugins {
173 let plugin_out_dir = out_dir.join("plugins").join(name);
174 fs::create_dir_all(&plugin_out_dir)?;
175
176 let mut permission_files = if plugin.commands.is_empty() {
177 Vec::new()
178 } else {
179 let autogenerated = tauri_utils::acl::build::autogenerate_command_permissions(
180 &plugin_out_dir,
181 plugin.commands,
182 "",
183 false,
184 );
185
186 let default_permissions = plugin.default.map(|default| match default {
187 DefaultPermissionRule::AllowAllCommands => autogenerated.allowed,
188 DefaultPermissionRule::Allow(permissions) => permissions,
189 });
190 if let Some(default_permissions) = default_permissions {
191 let default_permissions = default_permissions
192 .iter()
193 .map(|p| format!("\"{p}\""))
194 .collect::<Vec<String>>()
195 .join(",");
196 let default_permission = format!(
197 r###"# Automatically generated - DO NOT EDIT!
198[default]
199permissions = [{default_permissions}]
200"###
201 );
202
203 let default_permission_path = plugin_out_dir.join("default.toml");
204
205 write_if_changed(&default_permission_path, default_permission)
206 .unwrap_or_else(|_| panic!("unable to autogenerate {default_permission_path:?}"));
207 }
208
209 tauri_utils::acl::build::define_permissions(
210 &PathBuf::from(glob::Pattern::escape(&plugin_out_dir.to_string_lossy()))
211 .join("*")
212 .to_string_lossy(),
213 name,
214 &plugin_out_dir,
215 |_| true,
216 )?
217 };
218
219 if let Some(pattern) = plugin.permissions_path_pattern {
220 permission_files.extend(tauri_utils::acl::build::define_permissions(
221 pattern,
222 name,
223 &plugin_out_dir,
224 |_| true,
225 )?);
226 } else {
227 let default_permissions_path = Path::new("permissions").join(name);
228 if default_permissions_path.exists() {
229 println!(
230 "cargo:rerun-if-changed={}",
231 default_permissions_path.display()
232 );
233 }
234 permission_files.extend(tauri_utils::acl::build::define_permissions(
235 &PathBuf::from(glob::Pattern::escape(
236 &default_permissions_path.to_string_lossy(),
237 ))
238 .join("**")
239 .join("*")
240 .to_string_lossy(),
241 name,
242 &plugin_out_dir,
243 |_| true,
244 )?);
245 }
246
247 permission_files_map.insert(name.into(), permission_files.clone());
248
249 let manifest = tauri_utils::acl::manifest::Manifest::new(permission_files, None);
250 acl_manifests.insert(name.into(), manifest);
251 }
252
253 Ok(InlinedPluginsAcl {
254 manifests: acl_manifests,
255 permission_files: permission_files_map,
256 })
257}
258
259#[derive(Debug)]
260struct AppManifestAcl {
261 manifest: Manifest,
262 permission_files: Vec<PermissionFile>,
263}
264
265fn app_manifest_permissions(
266 out_dir: &Path,
267 manifest: AppManifest,
268 inlined_plugins: &HashMap<&'static str, InlinedPlugin>,
269) -> Result<AppManifestAcl> {
270 let app_out_dir = out_dir.join("app-manifest");
271 fs::create_dir_all(&app_out_dir)?;
272 let pkg_name = "__app__";
273
274 let mut permission_files = if manifest.commands.is_empty() {
275 Vec::new()
276 } else {
277 let autogenerated_path = Path::new("./permissions/autogenerated");
278 tauri_utils::acl::build::autogenerate_command_permissions(
279 autogenerated_path,
280 manifest.commands,
281 "",
282 false,
283 );
284 tauri_utils::acl::build::define_permissions(
285 &autogenerated_path.join("*").to_string_lossy(),
286 pkg_name,
287 &app_out_dir,
288 |_| true,
289 )?
290 };
291
292 if let Some(pattern) = manifest.permissions_path_pattern {
293 permission_files.extend(tauri_utils::acl::build::define_permissions(
294 pattern,
295 pkg_name,
296 &app_out_dir,
297 |_| true,
298 )?);
299 } else {
300 let default_permissions_path = Path::new("permissions");
301 if default_permissions_path.exists() {
302 println!(
303 "cargo:rerun-if-changed={}",
304 default_permissions_path.display()
305 );
306 }
307
308 let permissions_root = env::current_dir()?.join("permissions");
309 let inlined_plugins_permissions: Vec<_> = inlined_plugins
310 .keys()
311 .map(|name| permissions_root.join(name))
312 .flat_map(|p| p.canonicalize())
313 .collect();
314
315 permission_files.extend(tauri_utils::acl::build::define_permissions(
316 &default_permissions_path
317 .join("**")
318 .join("*")
319 .to_string_lossy(),
320 pkg_name,
321 &app_out_dir,
322 |p| {
324 !inlined_plugins_permissions
325 .iter()
326 .any(|inlined_path| p.starts_with(inlined_path))
327 },
328 )?);
329 }
330
331 Ok(AppManifestAcl {
332 permission_files: permission_files.clone(),
333 manifest: tauri_utils::acl::manifest::Manifest::new(permission_files, None),
334 })
335}
336
337fn validate_capabilities(
338 acl_manifests: &BTreeMap<String, Manifest>,
339 capabilities: &BTreeMap<String, Capability>,
340) -> Result<()> {
341 let target = tauri_utils::platform::Target::from_triple(&std::env::var("TARGET").unwrap());
342
343 for capability in capabilities.values() {
344 if !capability
345 .platforms
346 .as_ref()
347 .map(|platforms| platforms.contains(&target))
348 .unwrap_or(true)
349 {
350 continue;
351 }
352
353 for permission_entry in &capability.permissions {
354 let permission_id = permission_entry.identifier();
355
356 let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY);
357 let permission_name = permission_id.get_base();
358
359 let permission_exists = acl_manifests
360 .get(key)
361 .map(|manifest| {
362 permission_name == "default"
364 || manifest.permissions.contains_key(permission_name)
365 || manifest.permission_sets.contains_key(permission_name)
366 })
367 .unwrap_or(false);
368
369 if !permission_exists {
370 let mut available_permissions = Vec::new();
371 for (key, manifest) in acl_manifests {
372 let prefix = if key == APP_ACL_KEY {
373 "".to_string()
374 } else {
375 format!("{key}:")
376 };
377 if manifest.default_permission.is_some() {
378 available_permissions.push(format!("{prefix}default"));
379 }
380 for p in manifest.permissions.keys() {
381 available_permissions.push(format!("{prefix}{p}"));
382 }
383 for p in manifest.permission_sets.keys() {
384 available_permissions.push(format!("{prefix}{p}"));
385 }
386 }
387
388 anyhow::bail!(
389 "Permission {} not found, expected one of {}",
390 permission_id.get(),
391 available_permissions.join(", ")
392 );
393 }
394 }
395 }
396
397 Ok(())
398}
399
400pub fn build(out_dir: &Path, target: Target, attributes: &Attributes) -> super::Result<()> {
401 let mut acl_manifests = read_plugins_manifests()?;
402
403 let app_acl = app_manifest_permissions(
404 out_dir,
405 attributes.app_manifest,
406 &attributes.inlined_plugins,
407 )?;
408 let has_app_manifest = app_acl.manifest.default_permission.is_some()
409 || !app_acl.manifest.permission_sets.is_empty()
410 || !app_acl.manifest.permissions.is_empty();
411 if has_app_manifest {
412 acl_manifests.insert(APP_ACL_KEY.into(), app_acl.manifest);
413 }
414
415 let inline_plugins_acl = inline_plugins(out_dir, attributes.inlined_plugins.clone())?;
416
417 acl_manifests.extend(inline_plugins_acl.manifests);
418
419 let acl_manifests_path = save_acl_manifests(&acl_manifests)?;
420 fs::copy(acl_manifests_path, out_dir.join(ACL_MANIFESTS_FILE_NAME))?;
421
422 tauri_utils::acl::schema::generate_capability_schema(&acl_manifests, target)?;
423
424 let capabilities = if let Some(pattern) = attributes.capabilities_path_pattern {
425 tauri_utils::acl::build::parse_capabilities(pattern)?
426 } else {
427 println!("cargo:rerun-if-changed=capabilities");
428 tauri_utils::acl::build::parse_capabilities("./capabilities/**/*")?
429 };
430 validate_capabilities(&acl_manifests, &capabilities)?;
431
432 let capabilities_path = save_capabilities(&capabilities)?;
433 fs::copy(capabilities_path, out_dir.join(CAPABILITIES_FILE_NAME))?;
434
435 let mut permissions_map = inline_plugins_acl.permission_files;
436 if has_app_manifest {
437 permissions_map.insert(APP_ACL_KEY.to_string(), app_acl.permission_files);
438 }
439
440 tauri_utils::acl::build::generate_allowed_commands(out_dir, Some(capabilities), permissions_map)?;
441
442 Ok(())
443}