Skip to content

Commit

Permalink
feat(cli/add): add default permission to capabilities (#9124)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
amrbashir and lucasfernog committed Mar 13, 2024
1 parent acdd768 commit 7213b9e
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 189 deletions.
6 changes: 6 additions & 0 deletions .changes/acl-default-permission-verification.md
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions .changes/tauri-cli-add-default-perm.md
Original file line number Diff line number Diff line change
@@ -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>`.
36 changes: 20 additions & 16 deletions core/tauri-build/src/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)));
}
Expand Down Expand Up @@ -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);

Expand Down
11 changes: 2 additions & 9 deletions core/tauri-utils/src/acl/resolved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 7 additions & 4 deletions tooling/cli/src/acl/permission/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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)| {
Expand Down
2 changes: 1 addition & 1 deletion tooling/cli/src/acl/permission/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use clap::{Parser, Subcommand};

use crate::Result;

mod add;
pub mod add;
mod ls;
mod new;
mod rm;
Expand Down
177 changes: 76 additions & 101 deletions tooling/cli/src/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,67 @@
// 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,
};

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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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()"
Expand All @@ -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)?;
Expand All @@ -143,7 +164,6 @@ pub fn command(options: Options) -> Result<()> {
.arg("fmt")
.current_dir(&tauri_dir)
.status();

return Ok(());
}
}
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 7213b9e

Please sign in to comment.