-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tauri ACL/Allowlist v2 Implementation and Plugin System Refactor (#8428)
* tauri-plugin concept * wip * move command module to its own directory * wip: new command traits and generated code * wip: whip * wip: static dispatch there is a man standing behind me * wip * re-add authority * fix build [skip ci] * parse plugin permissions * merge permission files [skip ci] * parse capabilities [skip ci] * resolve acl (untested) [skip ci] * split functionality, add some docs * remove command2 stuff * actually check runtime authority * small fixes [skip ci] * add function to auto generate basic permission for a command [skip ci] * retrieve command scope, implement CommandArg [skip ci] * fix tests [skip ci] * global scope * lint * license headers [skip ci] * skip canonicalize * separate scope type in example * remove inlinedpermission struct [skip ci] * permission file schema * capabilities schema * move items from tauri-plugin to tauri-utils this allows tauri-plugin to depend on tauri directly again which will be used by the runtime feature as a superset to existing plugin traits * enable schema and glob [skip ci] * fix glob [skip ci] * fix capability schema [skip ci] * enhance schema for permission set possible values [skip ci] * permission set can reference other sets [skip ci] * setup tests for resolving ACL * fixture for permission set [skip ci] * remote context test and small fix[skip ci] * ignore empty scope [skip ci] * code review [skip ci] * lint [skip ci] * runtime fixes * readd schema feature on tauri-config-schema [skip ci] * remove plugin example from workspace, it breaks workspace features resolution [skip ci] * scope as array, add test [skip ci] * accept new shapshot [skip ci] * core plugin permissions, default is now a set * license headers * fix on windows * update global api * glob is no longer optional on tauri-utils * add missing permissions on api example [skip ci] * remove ipc scope and dangerous remote access config * lint * fix asset scope usage * create out dir [skip ci] * reuse cargo_pkg_name [skip ci] * capability window glob pattern [skip ci] * add platforms for capability [skip ci] * per platform schema [skip ci] * lint [skip ci] * rename allowlist build mod [skip ci] * check restricted visibility * simplify capability target [skip ci] * hide codegen build behind tauri-build::try_run * optimize build scripts [skip ci] * fix tests * tests for RuntimeAuthority::resolve_access * remote domain glob pattern * lint --------- Co-authored-by: Chip Reed <[email protected]> Co-authored-by: Lucas Nogueira <[email protected]> Co-authored-by: Lucas Nogueira <[email protected]> Co-authored-by: Lucas Nogueira <[email protected]>
- Loading branch information
1 parent
303708d
commit 3c2f79f
Showing
206 changed files
with
9,564 additions
and
819 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.