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

Tauri ACL/Allowlist v2 Implementation and Plugin System Refactor #8428

Merged
merged 74 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
da631ec
tauri-plugin concept
chippers Dec 6, 2023
57986ef
wip
chippers Dec 11, 2023
fcf2b37
Merge branch 'dev' into feat/allowlist-v2
chippers Dec 12, 2023
77ae621
move command module to its own directory
chippers Dec 12, 2023
e60d8a9
wip: new command traits and generated code
chippers Dec 18, 2023
de7f04e
Merge branch 'dev' into feat/allowlist-v2
chippers Dec 18, 2023
8ccf567
wip: whip
chippers Dec 21, 2023
ba778d3
wip: static dispatch
chippers Dec 21, 2023
1c5e822
wip
chippers Jan 4, 2024
29091ce
Merge branch 'dev' into feat/allowlist-v2
chippers Jan 4, 2024
022e728
re-add authority
chippers Jan 4, 2024
d26fa60
fix build [skip ci]
lucasfernog Jan 6, 2024
5db5217
parse plugin permissions
lucasfernog Jan 7, 2024
02268e4
merge permission files [skip ci]
lucasfernog-crabnebula Jan 7, 2024
ce1fa7d
parse capabilities [skip ci]
lucasfernog Jan 7, 2024
ceeb415
resolve acl (untested) [skip ci]
lucasfernog Jan 11, 2024
5a88921
split functionality, add some docs
lucasfernog Jan 11, 2024
60774d5
remove command2 stuff
lucasfernog Jan 11, 2024
650829b
actually check runtime authority
lucasfernog Jan 11, 2024
dc42a9d
small fixes [skip ci]
lucasfernog Jan 11, 2024
ddd898c
add function to auto generate basic permission for a command [skip ci]
lucasfernog Jan 11, 2024
dadb3d1
retrieve command scope, implement CommandArg [skip ci]
lucasfernog Jan 13, 2024
0922789
fix tests [skip ci]
lucasfernog Jan 13, 2024
72d32d9
global scope
lucasfernog Jan 14, 2024
9c950b3
lint
lucasfernog Jan 14, 2024
1e2c44b
license headers [skip ci]
lucasfernog Jan 14, 2024
cc0d016
skip canonicalize
lucasfernog Jan 14, 2024
2be4981
separate scope type in example
lucasfernog Jan 14, 2024
609fcfb
remove inlinedpermission struct [skip ci]
lucasfernog Jan 15, 2024
eba00d6
permission file schema
lucasfernog Jan 15, 2024
781243e
capabilities schema
lucasfernog Jan 15, 2024
6b7a0d0
move items from tauri-plugin to tauri-utils
chippers Jan 15, 2024
f460f27
enable schema and glob [skip ci]
lucasfernog Jan 15, 2024
f4df3ad
fix glob [skip ci]
lucasfernog Jan 15, 2024
59f800d
fix capability schema [skip ci]
lucasfernog Jan 15, 2024
c167438
enhance schema for permission set possible values [skip ci]
lucasfernog Jan 15, 2024
0e12677
permission set can reference other sets [skip ci]
lucasfernog Jan 16, 2024
a64c7b6
setup tests for resolving ACL
lucasfernog Jan 16, 2024
dd94e8c
fixture for permission set [skip ci]
lucasfernog Jan 16, 2024
fbd016d
remote context test and small fix[skip ci]
lucasfernog Jan 16, 2024
1338579
ignore empty scope [skip ci]
lucasfernog Jan 16, 2024
6fba15b
code review [skip ci]
lucasfernog Jan 17, 2024
3868add
lint [skip ci]
lucasfernog Jan 17, 2024
f2b6ff2
Merge branch 'dev' into feat/allowlist-v2
lucasfernog Jan 17, 2024
93257db
runtime fixes
lucasfernog Jan 17, 2024
87fe2b4
readd schema feature on tauri-config-schema [skip ci]
lucasfernog Jan 17, 2024
a9f0c9e
remove plugin example from workspace, it breaks workspace features re…
lucasfernog Jan 17, 2024
50651a5
scope as array, add test [skip ci]
lucasfernog Jan 17, 2024
5218e32
accept new shapshot [skip ci]
lucasfernog Jan 17, 2024
3edd138
core plugin permissions, default is now a set
lucasfernog Jan 17, 2024
faae483
license headers
lucasfernog Jan 17, 2024
6435bff
fix on windows
lucasfernog Jan 17, 2024
96bc5c1
update global api
lucasfernog Jan 17, 2024
c194b2e
glob is no longer optional on tauri-utils
lucasfernog Jan 17, 2024
5231a9e
add missing permissions on api example [skip ci]
lucasfernog Jan 17, 2024
24bcb31
remove ipc scope and dangerous remote access config
lucasfernog Jan 17, 2024
6af1c79
lint
lucasfernog Jan 17, 2024
25e5afc
fix asset scope usage
lucasfernog Jan 17, 2024
ac6d869
create out dir [skip ci]
lucasfernog Jan 17, 2024
9a32347
reuse cargo_pkg_name [skip ci]
lucasfernog Jan 18, 2024
38f6b99
capability window glob pattern [skip ci]
lucasfernog Jan 18, 2024
a1cdcd0
add platforms for capability [skip ci]
lucasfernog Jan 18, 2024
a146a89
per platform schema [skip ci]
lucasfernog Jan 18, 2024
053ade4
lint [skip ci]
lucasfernog Jan 18, 2024
4361330
rename allowlist build mod [skip ci]
lucasfernog Jan 18, 2024
be1f3a6
check restricted visibility
chippers Jan 17, 2024
8dd6ec1
simplify capability target [skip ci]
lucasfernog Jan 19, 2024
1b29ff7
hide codegen build behind tauri-build::try_run
lucasfernog Jan 20, 2024
0990921
optimize build scripts [skip ci]
lucasfernog Jan 20, 2024
3d6f347
merge from dev
lucasfernog Jan 22, 2024
0ccc8b8
fix tests
lucasfernog Jan 22, 2024
65992eb
tests for RuntimeAuthority::resolve_access
lucasfernog Jan 22, 2024
5eb1f7b
remote domain glob pattern
lucasfernog Jan 22, 2024
69abd20
lint
lucasfernog Jan 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ members = [
"core/tauri-build",
"core/tauri-codegen",
"core/tauri-config-schema",
"core/tauri-plugin",

# integration tests
"core/tests/restart",
"core/tests/acl",
]

exclude = [
Expand All @@ -22,6 +24,7 @@ exclude = [
"examples/web/core",
"examples/file-associations/src-tauri",
"examples/workspace",
"examples/plugins/tauri-plugin-example",
]

[workspace.package]
Expand Down
3 changes: 3 additions & 0 deletions core/tauri-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ walkdir = "2"
tauri-winres = "0.1"
semver = "1"
dirs-next = "2"
glob = "0.3"
toml = "0.8"
schemars = "0.8"

[target."cfg(target_os = \"macos\")".dependencies]
swift-rs = { version = "1.0.6", features = [ "build" ] }
Expand Down
174 changes: 174 additions & 0 deletions core/tauri-build/src/acl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::{
collections::BTreeMap,
fs::{copy, create_dir_all, File},
io::{BufWriter, Write},
path::PathBuf,
};

use anyhow::{Context, Result};
use schemars::{
schema::{InstanceType, Metadata, RootSchema, Schema, SchemaObject, SubschemaValidation},
schema_for,
};
use tauri_utils::{
acl::{build::CapabilityFile, capability::Capability, plugin::Manifest},
platform::Target,
};

const CAPABILITIES_SCHEMA_FILE_NAME: &str = "schema.json";
const CAPABILITIES_SCHEMA_FOLDER_NAME: &str = "schemas";

fn capabilities_schema(plugin_manifests: &BTreeMap<String, Manifest>) -> RootSchema {
let mut schema = schema_for!(CapabilityFile);

fn schema_from(plugin: &str, id: &str, description: Option<&str>) -> Schema {
Schema::Object(SchemaObject {
metadata: Some(Box::new(Metadata {
description: description
.as_ref()
.map(|d| format!("{plugin}:{id} -> {d}")),
..Default::default()
})),
instance_type: Some(InstanceType::String.into()),
enum_values: Some(vec![serde_json::Value::String(format!("{plugin}:{id}"))]),
..Default::default()
})
}

let mut permission_schemas = Vec::new();

for (plugin, manifest) in plugin_manifests {
for (set_id, set) in &manifest.permission_sets {
permission_schemas.push(schema_from(plugin, set_id, Some(&set.description)));
}

if let Some(default) = &manifest.default_permission {
permission_schemas.push(schema_from(
plugin,
"default",
Some(default.description.as_ref()),
));
}

for (permission_id, permission) in &manifest.permissions {
permission_schemas.push(schema_from(
plugin,
permission_id,
permission.description.as_deref(),
));
}
}

if let Some(Schema::Object(obj)) = schema.definitions.get_mut("Identifier") {
obj.object = None;
obj.instance_type = None;
obj.metadata.as_mut().map(|metadata| {
metadata
.description
.replace("Permission identifier".to_string());
metadata
});
obj.subschemas.replace(Box::new(SubschemaValidation {
one_of: Some(permission_schemas),
..Default::default()
}));
}

schema
}

pub fn generate_schema(
plugin_manifests: &BTreeMap<String, Manifest>,
target: Target,
) -> Result<()> {
let schema = capabilities_schema(plugin_manifests);
let schema_str = serde_json::to_string_pretty(&schema).unwrap();
let out_dir = PathBuf::from("capabilities").join(CAPABILITIES_SCHEMA_FOLDER_NAME);
create_dir_all(&out_dir).context("unable to create schema output directory")?;

let schema_path = out_dir.join(format!("{target}-{CAPABILITIES_SCHEMA_FILE_NAME}"));
let mut schema_file = BufWriter::new(File::create(&schema_path)?);
write!(schema_file, "{schema_str}")?;

copy(
schema_path,
out_dir.join(format!(
"{}-{CAPABILITIES_SCHEMA_FILE_NAME}",
if target.is_desktop() {
"desktop"
} else {
"mobile"
}
)),
)?;

Ok(())
}

pub fn get_plugin_manifests() -> Result<BTreeMap<String, Manifest>> {
let permission_map =
tauri_utils::acl::build::read_permissions().context("failed to read plugin permissions")?;

let mut processed = BTreeMap::new();
for (plugin_name, permission_files) in permission_map {
processed.insert(plugin_name, Manifest::from_files(permission_files));
}

Ok(processed)
}

pub fn validate_capabilities(
plugin_manifests: &BTreeMap<String, Manifest>,
capabilities: &BTreeMap<String, Capability>,
) -> Result<()> {
let target = tauri_utils::platform::Target::from_triple(&std::env::var("TARGET").unwrap());

for capability in capabilities.values() {
if !capability.platforms.contains(&target) {
continue;
}

for permission in &capability.permissions {
if let Some((plugin_name, permission_name)) = permission.get().split_once(':') {
let permission_exists = plugin_manifests
.get(plugin_name)
.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)
}
})
.unwrap_or(false);

if !permission_exists {
let mut available_permissions = Vec::new();
for (plugin, manifest) in plugin_manifests {
if manifest.default_permission.is_some() {
available_permissions.push(format!("{plugin}:default"));
}
for p in manifest.permissions.keys() {
available_permissions.push(format!("{plugin}:{p}"));
}
for p in manifest.permission_sets.keys() {
available_permissions.push(format!("{plugin}:{p}"));
}
}

anyhow::bail!(
"Permission {} not found, expected one of {}",
permission.get(),
available_permissions.join(", ")
);
}
}
}
}

Ok(())
}
43 changes: 38 additions & 5 deletions core/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use cargo_toml::Manifest;
use heck::AsShoutySnakeCase;

use tauri_utils::{
acl::build::parse_capabilities,
config::{BundleResources, Config, WebviewInstallMode},
resources::{external_binaries, ResourcePaths},
};
Expand All @@ -27,11 +28,12 @@ use std::{
path::{Path, PathBuf},
};

mod allowlist;
mod acl;
#[cfg(feature = "codegen")]
mod codegen;
/// Tauri configuration functions.
pub mod config;
mod manifest;
/// Mobile build functions.
pub mod mobile;
mod static_vcruntime;
Expand All @@ -40,6 +42,9 @@ mod static_vcruntime;
#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
pub use codegen::context::CodegenContext;

const PLUGIN_MANIFESTS_FILE_NAME: &str = "plugin-manifests.json";
const CAPABILITIES_FILE_NAME: &str = "capabilities.json";

fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
let from = from.as_ref();
let to = to.as_ref();
Expand Down Expand Up @@ -333,6 +338,7 @@ impl WindowsAttributes {
pub struct Attributes {
#[allow(dead_code)]
windows_attributes: WindowsAttributes,
capabilities_path_pattern: Option<&'static str>,
}

impl Attributes {
Expand All @@ -347,6 +353,13 @@ impl Attributes {
self.windows_attributes = windows_attributes;
self
}

/// Set the glob pattern to be used to find the capabilities.
#[must_use]
pub fn capabilities_path_pattern(mut self, pattern: &'static str) -> Self {
self.capabilities_path_pattern.replace(pattern);
self
}
}

/// Run all build time helpers for your Tauri Application.
Expand Down Expand Up @@ -399,8 +412,11 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
cfg_alias("desktop", !mobile);
cfg_alias("mobile", mobile);

let target_triple = std::env::var("TARGET").unwrap();
let target = tauri_utils::platform::Target::from_triple(&target_triple);

let mut config = serde_json::from_value(tauri_utils::config::parse::read_from(
tauri_utils::platform::Target::from_triple(&std::env::var("TARGET").unwrap()),
target,
std::env::current_dir().unwrap(),
)?)?;
if let Ok(env) = std::env::var("TAURI_CONFIG") {
Expand Down Expand Up @@ -441,13 +457,30 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
Manifest::complete_from_path(&mut manifest, Path::new("Cargo.toml"))?;
}

allowlist::check(&config, &mut manifest)?;
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());

let target_triple = std::env::var("TARGET").unwrap();
manifest::check(&config, &mut manifest)?;
let plugin_manifests = acl::get_plugin_manifests()?;
std::fs::write(
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 {
parse_capabilities("./capabilities/**/*")?
};
acl::generate_schema(&plugin_manifests, target)?;

acl::validate_capabilities(&plugin_manifests, &capabilities)?;

std::fs::write(
out_dir.join(CAPABILITIES_FILE_NAME),
serde_json::to_string(&capabilities)?,
)?;

println!("cargo:rustc-env=TAURI_ENV_TARGET_TRIPLE={target_triple}");

let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
// TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/5457>
let target_dir = out_dir
.parent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ mod tests {
},
),
] {
assert_eq!(super::features_diff(&current, &expected), result);
assert_eq!(manifest::features_diff(&current, &expected), result);
}
}
}
32 changes: 30 additions & 2 deletions core/tauri-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

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

Expand All @@ -10,6 +11,9 @@ use proc_macro2::TokenStream;
use quote::quote;
use sha2::{Digest, Sha256};

use tauri_utils::acl::capability::Capability;
use tauri_utils::acl::plugin::Manifest;
use tauri_utils::acl::resolved::Resolved;
use tauri_utils::assets::AssetKey;
use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
use tauri_utils::html::{
Expand All @@ -19,6 +23,9 @@ use tauri_utils::platform::Target;

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

const PLUGIN_MANIFESTS_FILE_NAME: &str = "plugin-manifests.json";
const CAPABILITIES_FILE_NAME: &str = "capabilities.json";

/// Necessary data needed by [`context_codegen`] to generate code for a Tauri application context.
pub struct ContextData {
pub dev: bool,
Expand Down Expand Up @@ -233,7 +240,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
}
};

let app_icon = if target == Target::Darwin && dev {
let app_icon = if target == Target::MacOS && dev {
let mut icon_path = find_icon(
&config,
&config_parent,
Expand Down Expand Up @@ -297,7 +304,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
};

#[cfg(target_os = "macos")]
let info_plist = if target == Target::Darwin && dev {
let info_plist = if target == Target::MacOS && dev {
let info_plist_path = config_parent.join("Info.plist");
let mut info_plist = if info_plist_path.exists() {
plist::Value::from_file(&info_plist_path)
Expand Down Expand Up @@ -373,6 +380,26 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
}
};

let acl_file_path = out_dir.join(PLUGIN_MANIFESTS_FILE_NAME);
let acl: BTreeMap<String, Manifest> = if acl_file_path.exists() {
let acl_file =
std::fs::read_to_string(acl_file_path).expect("failed to read plugin manifest map");
serde_json::from_str(&acl_file).expect("failed to parse plugin manifest map")
} else {
Default::default()
};

let capabilities_file_path = out_dir.join(CAPABILITIES_FILE_NAME);
let capabilities: 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_act = Resolved::resolve(acl, capabilities, target).expect("failed to resolve ACL");

Ok(quote!({
#[allow(unused_mut, clippy::let_and_return)]
let mut context = #root::Context::new(
Expand All @@ -383,6 +410,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
#package_info,
#info_plist,
#pattern,
#resolved_act
);
#with_tray_icon_code
context
Expand Down
Loading