Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codegen): allow defining additional capabilities, closes #8798 #8802

Merged
merged 12 commits into from
Feb 19, 2024
7 changes: 7 additions & 0 deletions .changes/capabilities-tauri-conf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"tauri-build": patch:breaking
"tauri-utils": patch:enhance
"tauri-codegen": patch:enhance
---

Added a new configuration option `tauri.conf.json > app > security > capabilities` to reference existing capabilities and inline new ones. If it is empty, all capabilities are still included preserving the current behavior.
6 changes: 6 additions & 0 deletions .changes/codegen-capabilities-attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-macros": patch:enhance
"tauri-codegen": patch:enhance
---

The `generate_context` proc macro now accepts a `capabilities` attribute where the value is an array of file paths that can be [conditionally compiled](https://doc.rust-lang.org/reference/conditional-compilation.html). These capabilities are added to the application along the capabilities defined in the Tauri configuration file.
5 changes: 5 additions & 0 deletions .changes/context-runtime-authority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": patch:enhance
---

The `Context` struct now includes the runtime authority instead of the resolved ACL. This does not impact most applications.
5 changes: 5 additions & 0 deletions .changes/tauri-build-codegen-capabilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-build": patch:enhance
---

Added `CodegenContext::capability` to include a capability file dynamically.
5 changes: 5 additions & 0 deletions .changes/tauri-utils-capability-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": patch:enhance
---

Refactored the capability types and resolution algorithm.
6 changes: 6 additions & 0 deletions .changes/update-app-template-capabilities-conf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tauri-apps/cli": patch:enhance
"tauri-cli": patch:enhance
---

Update app template following capabilities configuration change.
5 changes: 4 additions & 1 deletion core/tauri-build/src/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ use schemars::{
schema_for,
};
use tauri_utils::{
acl::{build::CapabilityFile, capability::Capability, plugin::Manifest},
acl::{
capability::{Capability, CapabilityFile},
plugin::Manifest,
},
platform::Target,
};

Expand Down
17 changes: 15 additions & 2 deletions core/tauri-build/src/codegen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use std::{
env::var,
fs::{create_dir_all, File},
io::{BufWriter, Write},
path::PathBuf,
path::{Path, PathBuf},
};
use tauri_codegen::{context_codegen, ContextData};
use tauri_codegen::{context_codegen, Capabilities, ContextData};
use tauri_utils::config::FrontendDist;

// TODO docs
Expand All @@ -20,6 +20,7 @@ pub struct CodegenContext {
dev: bool,
config_path: PathBuf,
out_file: PathBuf,
capabilities: Option<Vec<PathBuf>>,
}

impl Default for CodegenContext {
Expand All @@ -28,6 +29,7 @@ impl Default for CodegenContext {
dev: false,
config_path: PathBuf::from("tauri.conf.json"),
out_file: PathBuf::from("tauri-build-context.rs"),
capabilities: None,
}
}
}
Expand Down Expand Up @@ -74,6 +76,16 @@ impl CodegenContext {
self
}

/// Adds a capability file to the generated context.
#[must_use]
pub fn capability<P: AsRef<Path>>(mut self, path: P) -> Self {
self
.capabilities
.get_or_insert_with(Default::default)
.push(path.as_ref().to_path_buf());
self
}

/// Generate the code and write it to the output file - returning the path it was saved to.
///
/// Unless you are doing something special with this builder, you don't need to do anything with
Expand Down Expand Up @@ -125,6 +137,7 @@ impl CodegenContext {
// it's very hard to have a build script for unit tests, so assume this is always called from
// outside the tauri crate, making the ::tauri root valid.
root: quote::quote!(::tauri),
capabilities: self.capabilities.map(Capabilities::FromFiles),
})?;

// get the full output file path
Expand Down
1 change: 1 addition & 0 deletions core/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
out_dir.join(PLUGIN_MANIFESTS_FILE_NAME),
serde_json::to_string(&plugin_manifests)?,
)?;

let capabilities = if let Some(pattern) = attributes.capabilities_path_pattern {
parse_capabilities(pattern)?
} else {
Expand Down
1 change: 1 addition & 0 deletions core/tauri-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ sha2 = "0.10"
base64 = "0.21"
proc-macro2 = "1"
quote = "1"
syn = "2"
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
tauri-utils = { version = "2.0.0-beta.1", path = "../tauri-utils", features = [ "build" ] }
Expand Down
110 changes: 104 additions & 6 deletions core/tauri-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@
// SPDX-License-Identifier: MIT

use std::collections::BTreeMap;
use std::convert::identity;
use std::path::{Path, PathBuf};
use std::{ffi::OsStr, str::FromStr};

use base64::Engine;
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, TokenStreamExt};
use sha2::{Digest, Sha256};

use tauri_utils::acl::capability::Capability;
use syn::Attribute;
use tauri_utils::acl::capability::{Capability, CapabilityFile};
use tauri_utils::acl::plugin::Manifest;
use tauri_utils::acl::resolved::Resolved;
use tauri_utils::assets::AssetKey;
use tauri_utils::config::{Config, FrontendDist, PatternKind};
use tauri_utils::config::{CapabilityEntry, Config, FrontendDist, PatternKind};
use tauri_utils::html::{
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
};
use tauri_utils::platform::Target;
use tauri_utils::tokens::{map_lit, str_lit};

use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError};

Expand All @@ -32,6 +35,23 @@ pub struct ContextData {
pub config: Config,
pub config_parent: PathBuf,
pub root: TokenStream,
pub capabilities: Option<Capabilities>,
}

/// Additional capabilities to include.
pub enum Capabilities {
/// Capabilities from Rust tokens.
FromTokens(Vec<CapabilityToken>),
/// Capabilities from files.
FromFiles(Vec<PathBuf>),
}

/// Capability that was parsed from a stream of Rust tokens.
pub struct CapabilityToken {
/// Attributes.
pub attrs: Vec<Attribute>,
/// Path to the capability file.
pub path: String,
}

fn map_core_assets(
Expand Down Expand Up @@ -126,6 +146,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
config,
config_parent,
root,
capabilities: additional_capabilities,
} = data;

let target = std::env::var("TARGET")
Expand Down Expand Up @@ -381,15 +402,92 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
};

let capabilities_file_path = out_dir.join(CAPABILITIES_FILE_NAME);
let capabilities: BTreeMap<String, Capability> = if capabilities_file_path.exists() {
let mut capabilities_from_files: BTreeMap<String, Capability> = if capabilities_file_path.exists()
{
let capabilities_file =
std::fs::read_to_string(capabilities_file_path).expect("failed to read capabilities");
serde_json::from_str(&capabilities_file).expect("failed to parse capabilities")
} else {
Default::default()
};

let resolved_acl = Resolved::resolve(acl, capabilities, target).expect("failed to resolve ACL");
let capabilities = if config.app.security.capabilities.is_empty() {
capabilities_from_files
} else {
let mut capabilities = BTreeMap::new();
for capability_entry in &config.app.security.capabilities {
match capability_entry {
CapabilityEntry::Inlined(capability) => {
capabilities.insert(capability.identifier.clone(), capability.clone());
}
CapabilityEntry::Reference(id) => {
let capability = capabilities_from_files
.remove(id)
.unwrap_or_else(|| panic!("capability with identifier {id} not found"));
capabilities.insert(id.clone(), capability);
}
}
}
capabilities
};

let acl_tokens = map_lit(
quote! { ::std::collections::BTreeMap },
&acl,
str_lit,
identity,
);

let runtime_authority = match additional_capabilities {
Some(Capabilities::FromFiles(paths)) => {
let mut capabilities = capabilities;

for path in paths {
let capability = CapabilityFile::load(&path)
.unwrap_or_else(|e| panic!("failed to read capability {}: {e}", path.display()));
match capability {
CapabilityFile::Capability(c) => {
capabilities.insert(c.identifier.clone(), c);
}
CapabilityFile::List {
capabilities: capabilities_list,
} => {
capabilities.extend(
capabilities_list
.into_iter()
.map(|c| (c.identifier.clone(), c)),
);
}
}
}

let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved))
}
Some(Capabilities::FromTokens(tokens)) => {
let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");

let mut additions = quote!();

for CapabilityToken { attrs, path } in tokens {
let path = path.as_str();
additions.append_all(quote!(
#(#attrs),*
authority.add_capability(include_str!(#path).parse().expect("invalid capability file")).expect("failed to add capability");
));
}

quote!({
let mut authority = #root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved);
#additions
authority
})
}
None => {
let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL");
quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved))
}
};

Ok(quote!({
#[allow(unused_mut, clippy::let_and_return)]
Expand All @@ -401,7 +499,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
#package_info,
#info_plist,
#pattern,
#resolved_acl
#runtime_authority
);
#with_tray_icon_code
context
Expand Down
2 changes: 1 addition & 1 deletion core/tauri-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
)]

pub use self::context::{context_codegen, ContextData};
pub use self::context::{context_codegen, Capabilities, CapabilityToken, ContextData};
use std::{
borrow::Cow,
path::{Path, PathBuf},
Expand Down