Browse Source

feat(cli/add): add default permission to capabilities (#9124)

* feat(cli/add): add default permission to capabilities

also cleanup `tauri add` command

* license headers & clippy

* print permission name

* do not error out if default permission is not set

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Amr Bashir 1 year ago
parent
commit
7213b9e472

+ 6 - 0
.changes/acl-default-permission-verification.md

@@ -0,0 +1,6 @@
+---
+"tauri-build": patch:enhance
+"tauri-utils": patch:enhance
+---
+
+Fallback to an empty permission set if the plugin did not define its `default` permissions.

+ 6 - 0
.changes/tauri-cli-add-default-perm.md

@@ -0,0 +1,6 @@
+---
+'tauri-cli': 'patch:feat'
+'@tauri-apps/cli': 'patch:feat'
+---
+
+Add default permission for a plugin to capabilities when using `tauri add <plugin>`.

+ 20 - 16
core/tauri-build/src/acl.rs

@@ -131,13 +131,14 @@ fn capabilities_schema(acl_manifests: &BTreeMap<String, Manifest>) -> RootSchema
       permission_schemas.push(schema_from(key, set_id, Some(&set.description)));
     }
 
-    if let Some(default) = &manifest.default_permission {
-      permission_schemas.push(schema_from(
-        key,
-        "default",
-        Some(default.description.as_ref()),
-      ));
-    }
+    permission_schemas.push(schema_from(
+      key,
+      "default",
+      manifest
+        .default_permission
+        .as_ref()
+        .map(|d| d.description.as_ref()),
+    ));
 
     for (permission_id, permission) in &manifest.permissions {
       permission_schemas.push(schema_from(
@@ -198,9 +199,14 @@ fn capabilities_schema(acl_manifests: &BTreeMap<String, Manifest>) -> RootSchema
           };
 
           let mut permission_schemas = Vec::new();
-          if let Some(default) = &manifest.default_permission {
-            permission_schemas.push(schema_from(key, "default", Some(&default.description)));
-          }
+          permission_schemas.push(schema_from(
+            key,
+            "default",
+            manifest
+              .default_permission
+              .as_ref()
+              .map(|d| d.description.as_ref()),
+          ));
           for set in manifest.permission_sets.values() {
             permission_schemas.push(schema_from(key, &set.identifier, Some(&set.description)));
           }
@@ -471,12 +477,10 @@ pub fn validate_capabilities(
       let permission_exists = acl_manifests
         .get(key)
         .map(|manifest| {
-          if permission_name == "default" {
-            manifest.default_permission.is_some()
-          } else {
-            manifest.permissions.contains_key(permission_name)
-              || manifest.permission_sets.contains_key(permission_name)
-          }
+          // the default permission is always treated as valid, the CLI automatically adds it on the `tauri add` command
+          permission_name == "default"
+            || manifest.permissions.contains_key(permission_name)
+            || manifest.permission_sets.contains_key(permission_name)
         })
         .unwrap_or(false);
 

+ 2 - 9
core/tauri-utils/src/acl/resolved.rs

@@ -367,15 +367,8 @@ fn get_permissions<'a>(
     manifest
       .default_permission
       .as_ref()
-      .ok_or_else(|| Error::UnknownPermission {
-        key: if key == APP_ACL_KEY {
-          "app manifest".to_string()
-        } else {
-          key.to_string()
-        },
-        permission: permission_name.to_string(),
-      })
-      .and_then(|default| get_permission_set_permissions(manifest, default))
+      .map(|default| get_permission_set_permissions(manifest, default))
+      .unwrap_or_else(|| Ok(Vec::new()))
   } else if let Some(set) = manifest.permission_sets.get(permission_name) {
     get_permission_set_permissions(manifest, set)
   } else if let Some(permission) = manifest.permissions.get(permission_name) {

+ 7 - 4
tooling/cli/src/acl/permission/add.rs

@@ -80,10 +80,10 @@ fn capability_from_path<P: AsRef<Path>>(path: P) -> Option<TomlOrJson> {
 #[derive(Debug, Parser)]
 #[clap(about = "Add a permission to capabilities")]
 pub struct Options {
-  /// Permission to remove.
-  identifier: String,
+  /// Permission to add.
+  pub identifier: String,
   /// Capability to add the permission to.
-  capability: Option<String>,
+  pub capability: Option<String>,
 }
 
 pub fn command(options: Options) -> Result<()> {
@@ -114,7 +114,10 @@ pub fn command(options: Options) -> Result<()> {
 
   let mut capabilities = if capabilities.len() > 1 {
     let selections = prompts::multiselect(
-      "Choose which capabilities to add the permission to:",
+      &format!(
+        "Choose which capabilities to add the permission `{}` to:",
+        options.identifier
+      ),
       capabilities
         .iter()
         .map(|(c, p)| {

+ 1 - 1
tooling/cli/src/acl/permission/mod.rs

@@ -6,7 +6,7 @@ use clap::{Parser, Subcommand};
 
 use crate::Result;
 
-mod add;
+pub mod add;
 mod ls;
 mod new;
 mod rm;

+ 76 - 101
tooling/cli/src/add.rs

@@ -2,15 +2,15 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use anyhow::Context;
 use clap::Parser;
 use colored::Colorize;
 use regex::Regex;
 
 use crate::{
+  acl,
   helpers::{
     app_paths::{app_dir, tauri_dir},
-    cross_command,
+    cargo,
     npm::PackageManager,
   },
   Result,
@@ -18,6 +18,51 @@ use crate::{
 
 use std::{collections::HashMap, process::Command};
 
+#[derive(Default)]
+struct PluginMetadata {
+  desktop_only: bool,
+  rust_only: bool,
+  builder: bool,
+}
+
+// known plugins with particular cases
+fn plugins() -> HashMap<&'static str, PluginMetadata> {
+  let mut plugins: HashMap<&'static str, PluginMetadata> = HashMap::new();
+
+  // desktop-only
+  for p in [
+    "authenticator",
+    "cli",
+    "global-shortcut",
+    "updater",
+    "window-state",
+  ] {
+    plugins.entry(p).or_default().desktop_only = true;
+  }
+
+  // uses builder pattern
+  for p in [
+    "global-shortcut",
+    "localhost",
+    "log",
+    "sql",
+    "store",
+    "stronghold",
+    "updater",
+    "window-state",
+  ] {
+    plugins.entry(p).or_default().builder = true;
+  }
+
+  // rust-only
+  #[allow(clippy::single_element_loop)]
+  for p in ["localhost"] {
+    plugins.entry(p).or_default().rust_only = true;
+  }
+
+  plugins
+}
+
 #[derive(Debug, Parser)]
 #[clap(about = "Add a tauri plugin to the project")]
 pub struct Options {
@@ -45,43 +90,16 @@ pub fn command(options: Options) -> Result<()> {
 
   let tauri_dir = tauri_dir();
 
-  let mut cargo = Command::new("cargo");
-  cargo.current_dir(&tauri_dir).arg("add").arg(&crate_name);
-
-  if options.tag.is_some() || options.rev.is_some() || options.branch.is_some() {
-    cargo
-      .arg("--git")
-      .arg("https://github.com/tauri-apps/plugins-workspace");
-  }
-
-  if metadata.desktop_only {
-    cargo
-      .arg("--target")
-      .arg(r#"cfg(not(any(target_os = "android", target_os = "ios")))"#);
-  }
-
-  let npm_spec = match (options.tag, options.rev, options.branch) {
-    (Some(tag), None, None) => {
-      cargo.args(["--tag", &tag]);
-      format!("tauri-apps/tauri-plugin-{plugin}#{tag}")
-    }
-    (None, Some(rev), None) => {
-      cargo.args(["--rev", &rev]);
-      format!("tauri-apps/tauri-plugin-{plugin}#{rev}")
-    }
-    (None, None, Some(branch)) => {
-      cargo.args(["--branch", &branch]);
-      format!("tauri-apps/tauri-plugin-{plugin}#{branch}")
-    }
-    (None, None, None) => npm_name,
-    _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"),
-  };
-
-  log::info!("Installing Cargo dependency {crate_name}...");
-  let status = cargo.status().context("failed to run `cargo add`")?;
-  if !status.success() {
-    anyhow::bail!("Failed to install Cargo dependency");
-  }
+  cargo::install_one(cargo::CargoInstallOptions {
+    name: &crate_name,
+    branch: options.branch.as_deref(),
+    rev: options.rev.as_deref(),
+    tag: options.tag.as_deref(),
+    cwd: Some(&tauri_dir),
+    target: metadata
+      .desktop_only
+      .then_some(r#"cfg(not(any(target_os = "android", target_os = "ios")))"#),
+  })?;
 
   if !metadata.rust_only {
     if let Some(manager) = std::panic::catch_unwind(app_dir)
@@ -90,26 +108,28 @@ pub fn command(options: Options) -> Result<()> {
       .map(PackageManager::from_project)
       .and_then(|managers| managers.into_iter().next())
     {
-      let mut cmd = match manager {
-        PackageManager::Npm => cross_command("npm"),
-        PackageManager::Pnpm => cross_command("pnpm"),
-        PackageManager::Yarn => cross_command("yarn"),
-        PackageManager::YarnBerry => cross_command("yarn"),
-        PackageManager::Bun => cross_command("bun"),
+      let npm_spec = match (options.tag, options.rev, options.branch) {
+        (Some(tag), None, None) => {
+          format!("tauri-apps/tauri-plugin-{plugin}#{tag}")
+        }
+        (None, Some(rev), None) => {
+          format!("tauri-apps/tauri-plugin-{plugin}#{rev}")
+        }
+        (None, None, Some(branch)) => {
+          format!("tauri-apps/tauri-plugin-{plugin}#{branch}")
+        }
+        (None, None, None) => npm_name,
+        _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"),
       };
-
-      cmd.arg("add").arg(&npm_spec);
-
-      log::info!("Installing NPM dependency {npm_spec}...");
-      let status = cmd
-        .status()
-        .with_context(|| format!("failed to run {manager}"))?;
-      if !status.success() {
-        anyhow::bail!("Failed to install NPM dependency");
-      }
+      manager.install(&[npm_spec])?;
     }
   }
 
+  let _ = acl::permission::add::command(acl::permission::add::Options {
+    identifier: format!("{plugin}:default"),
+    capability: None,
+  });
+
   // add plugin init code to main.rs or lib.rs
   let plugin_init_fn = if plugin == "stronghold" {
     "Builder::new(|pass| todo!()).build()"
@@ -119,6 +139,7 @@ pub fn command(options: Options) -> Result<()> {
     "init()"
   };
   let plugin_init = format!(".plugin(tauri_plugin_{plugin_snake_case}::{plugin_init_fn})");
+
   let re = Regex::new(r"(tauri\s*::\s*Builder\s*::\s*default\(\))(\s*)")?;
   for file in [tauri_dir.join("src/main.rs"), tauri_dir.join("src/lib.rs")] {
     let contents = std::fs::read_to_string(&file)?;
@@ -143,7 +164,6 @@ pub fn command(options: Options) -> Result<()> {
         .arg("fmt")
         .current_dir(&tauri_dir)
         .status();
-
       return Ok(());
     }
   }
@@ -176,48 +196,3 @@ pub fn command(options: Options) -> Result<()> {
 
   Ok(())
 }
-
-#[derive(Default)]
-struct PluginMetadata {
-  desktop_only: bool,
-  rust_only: bool,
-  builder: bool,
-}
-
-// known plugins with particular cases
-fn plugins() -> HashMap<&'static str, PluginMetadata> {
-  let mut plugins: HashMap<&'static str, PluginMetadata> = HashMap::new();
-
-  // desktop-only
-  for p in [
-    "authenticator",
-    "cli",
-    "global-shortcut",
-    "updater",
-    "window-state",
-  ] {
-    plugins.entry(p).or_default().desktop_only = true;
-  }
-
-  // uses builder pattern
-  for p in [
-    "global-shortcut",
-    "localhost",
-    "log",
-    "sql",
-    "store",
-    "stronghold",
-    "updater",
-    "window-state",
-  ] {
-    plugins.entry(p).or_default().builder = true;
-  }
-
-  // rust-only
-  #[allow(clippy::single_element_loop)]
-  for p in ["localhost"] {
-    plugins.entry(p).or_default().rust_only = true;
-  }
-
-  plugins
-}

+ 87 - 0
tooling/cli/src/helpers/cargo.rs

@@ -0,0 +1,87 @@
+// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::{path::Path, process::Command};
+
+use anyhow::Context;
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct CargoInstallOptions<'a> {
+  pub name: &'a str,
+  pub rev: Option<&'a str>,
+  pub tag: Option<&'a str>,
+  pub branch: Option<&'a str>,
+  pub cwd: Option<&'a std::path::Path>,
+  pub target: Option<&'a str>,
+}
+
+pub fn install(dependencies: &[String], cwd: Option<&Path>) -> crate::Result<()> {
+  let dependencies_str = if dependencies.len() > 1 {
+    "dependencies"
+  } else {
+    "dependency"
+  };
+  log::info!(
+    "Installing Cargo {dependencies_str} {}...",
+    dependencies
+      .iter()
+      .map(|d| format!("\"{d}\""))
+      .collect::<Vec<_>>()
+      .join(", ")
+  );
+
+  let mut cmd = Command::new("cargo");
+  cmd.arg("add").args(dependencies);
+
+  if let Some(cwd) = cwd {
+    cmd.current_dir(cwd);
+  }
+
+  let status = cmd.status().with_context(|| "failed to run cargo")?;
+
+  if !status.success() {
+    anyhow::bail!("Failed to install Cargo {dependencies_str}");
+  }
+
+  Ok(())
+}
+
+pub fn install_one(options: CargoInstallOptions) -> crate::Result<()> {
+  let mut cargo = Command::new("cargo");
+  cargo.args(["add", options.name]);
+
+  if options.tag.is_some() || options.rev.is_some() || options.branch.is_some() {
+    cargo.args(["--git", "https://github.com/tauri-apps/plugins-workspace"]);
+  }
+
+  match (options.tag, options.rev, options.branch) {
+    (Some(tag), None, None) => {
+      cargo.args(["--tag", &tag]);
+    }
+    (None, Some(rev), None) => {
+      cargo.args(["--rev", &rev]);
+    }
+    (None, None, Some(branch)) => {
+      cargo.args(["--branch", &branch]);
+    }
+    (None, None, None) => {}
+    _ => anyhow::bail!("Only one of --tag, --rev and --branch can be specified"),
+  };
+
+  if let Some(target) = options.target {
+    cargo.args(["--target", target]);
+  }
+
+  if let Some(cwd) = options.cwd {
+    cargo.current_dir(cwd);
+  }
+
+  log::info!("Installing Cargo dependency \"{}\"...", options.name);
+  let status = cargo.status().context("failed to run `cargo add`")?;
+  if !status.success() {
+    anyhow::bail!("Failed to install Cargo dependency");
+  }
+
+  Ok(())
+}

+ 1 - 0
tooling/cli/src/helpers/mod.rs

@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: MIT
 
 pub mod app_paths;
+pub mod cargo;
 pub mod config;
 pub mod flock;
 pub mod framework;

+ 39 - 43
tooling/cli/src/helpers/npm.rs

@@ -2,8 +2,10 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::{helpers::cross_command, Result};
-use std::{fmt::Display, path::Path, process::ExitStatus};
+use anyhow::Context;
+
+use crate::helpers::cross_command;
+use std::{fmt::Display, path::Path, process::Command};
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
 pub enum PackageManager {
@@ -69,48 +71,42 @@ impl PackageManager {
     found
   }
 
-  pub fn install(&self, dependencies: &[String]) -> Result<ExitStatus> {
+  fn cross_command(&self) -> Command {
     match self {
-      PackageManager::Yarn => {
-        let mut cmd = cross_command("yarn");
-        cmd
-          .arg("add")
-          .args(dependencies)
-          .status()
-          .map_err(Into::into)
-      }
-      PackageManager::YarnBerry => {
-        let mut cmd = cross_command("yarn");
-        cmd
-          .arg("add")
-          .args(dependencies)
-          .status()
-          .map_err(Into::into)
-      }
-      PackageManager::Npm => {
-        let mut cmd = cross_command("npm");
-        cmd
-          .arg("install")
-          .args(dependencies)
-          .status()
-          .map_err(Into::into)
-      }
-      PackageManager::Pnpm => {
-        let mut cmd = cross_command("pnpm");
-        cmd
-          .arg("install")
-          .args(dependencies)
-          .status()
-          .map_err(Into::into)
-      }
-      PackageManager::Bun => {
-        let mut cmd = cross_command("bun");
-        cmd
-          .arg("install")
-          .args(dependencies)
-          .status()
-          .map_err(Into::into)
-      }
+      PackageManager::Yarn => cross_command("yarn"),
+      PackageManager::YarnBerry => cross_command("yarn"),
+      PackageManager::Npm => cross_command("npm"),
+      PackageManager::Pnpm => cross_command("pnpm"),
+      PackageManager::Bun => cross_command("bun"),
     }
   }
+
+  pub fn install(&self, dependencies: &[String]) -> crate::Result<()> {
+    let dependencies_str = if dependencies.len() > 1 {
+      "dependencies"
+    } else {
+      "dependency"
+    };
+    log::info!(
+      "Installing NPM {dependencies_str} {}...",
+      dependencies
+        .iter()
+        .map(|d| format!("\"{d}\""))
+        .collect::<Vec<_>>()
+        .join(", ")
+    );
+
+    let status = self
+      .cross_command()
+      .arg("add")
+      .args(dependencies)
+      .status()
+      .with_context(|| format!("failed to run {self}"))?;
+
+    if !status.success() {
+      anyhow::bail!("Failed to install NPM {dependencies_str}");
+    }
+
+    Ok(())
+  }
 }

+ 2 - 15
tooling/cli/src/migrate/frontend.rs

@@ -3,14 +3,13 @@
 // SPDX-License-Identifier: MIT
 
 use crate::{
-  helpers::{app_paths::walk_builder, npm::PackageManager},
+  helpers::{app_paths::walk_builder, cargo, npm::PackageManager},
   Result,
 };
 
 use std::{
   fs::{read_to_string, write},
   path::Path,
-  process::Command,
 };
 
 const CORE_API_MODULES: &[&str] = &["dpi", "event", "path", "core", "window", "mocks"];
@@ -78,23 +77,11 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> {
   }
 
   if !new_npm_packages.is_empty() {
-    log::info!(
-      "Installing NPM packages for plugins: {}",
-      new_npm_packages.join(", ")
-    );
     pm.install(&new_npm_packages)?;
   }
 
   if !new_cargo_packages.is_empty() {
-    log::info!(
-      "Installing Cargo dependencies for plugins: {}",
-      new_cargo_packages.join(", ")
-    );
-    Command::new("cargo")
-      .arg("add")
-      .args(new_cargo_packages)
-      .current_dir(tauri_dir)
-      .status()?;
+    cargo::install(&new_cargo_packages, Some(tauri_dir))?;
   }
 
   Ok(())