initial draft for runtime authority

This commit is contained in:
Lucas Nogueira 2023-07-26 09:01:37 -03:00
parent 8467f138bb
commit 708cb9fa28
No known key found for this signature in database
GPG Key ID: FFEA6C72E73482F1
12 changed files with 113 additions and 34 deletions

View File

@ -489,18 +489,20 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
.entry(member.clone())
.or_insert_with(|| MemberResolution {
member: member.clone(),
capabilities: Default::default(),
commands: Default::default(),
});
for capability in &namespace.capabilities {
let (plugin, capability) = manifests.find_capability(capability).unwrap_or_else(|| {
panic!("could not find capability specification matching id {capability}")
});
let resolved_capability = member_resolution
.capabilities
.entry(capability.id.clone())
.or_default();
resolved_capability.features.extend(capability.features);
member_resolution.commands.extend(
capability
.features
.into_iter()
.map(|f| format!("plugin:{plugin}|{f}"))
.collect::<Vec<_>>(),
);
}
}
}

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
use std::path::{Path, PathBuf};
use std::{ffi::OsStr, str::FromStr};
use std::{ffi::OsStr, fs::read_to_string, str::FromStr};
use base64::Engine;
use proc_macro2::TokenStream;
@ -15,6 +15,7 @@ use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
use tauri_utils::html::{
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
};
use tauri_utils::namespace::NamespaceLockFile;
use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError};
@ -421,6 +422,15 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
}
};
let lockfile_path = config_parent.join("tauri.namespace.lock");
let lockfile: NamespaceLockFile = if lockfile_path.exists() {
let lockfile = read_to_string(&lockfile_path)?;
serde_json::from_str(&lockfile)?
} else {
Default::default()
};
let runtime_authority = runtime_authority_codegen(&root, lockfile);
Ok(quote!({
#[allow(unused_mut, clippy::let_and_return)]
let mut context = #root::Context::new(
@ -431,12 +441,30 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
#package_info,
#info_plist,
#pattern,
#runtime_authority
);
#with_system_tray_icon_code
context
}))
}
fn runtime_authority_codegen(root: &TokenStream, lockfile: NamespaceLockFile) -> TokenStream {
let add_members = lockfile.resolution.iter().map(|r| {
let member = &r.member;
let commands = &r.commands;
let resolution = quote!(#root::runtime_authority::MemberResolution {
member: #member.into(),
commands: vec![#(#commands)*.into()]
});
quote!(authority.add_member(#resolution);)
});
quote!({
let mut authority = #root::runtime_authority::RuntimeAuthority::new();
#(#add_members)*
authority
})
}
fn ico_icon<P: AsRef<Path>>(
root: &TokenStream,
out_dir: &Path,

View File

@ -59,6 +59,12 @@ pub enum EmbeddedAssetsError {
#[error("version error: {0}")]
Version(#[from] semver::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
}
/// Represent a directory of assets that are compressed and embedded.

View File

@ -45,7 +45,7 @@ pub enum CodegenConfigError {
ConfigError(#[from] ConfigError),
}
/// Get the [`Config`] from the `TAURI_CONFIG` environmental variable, or read from the passed path.
/// Get the [`Config`] from the passed path and merge it with the value from the `TAURI_CONFIG` environment variable.
///
/// If the passed path is relative, it should be relative to the current working directory of the
/// compiling crate.
@ -67,8 +67,8 @@ pub fn get_config(path: &Path) -> Result<(Config, PathBuf), CodegenConfigError>
// it is impossible for the content of two separate configs to get mixed up. The chances are
// already unlikely unless the developer goes out of their way to run the cli on a different
// project than the target crate.
let mut config =
serde_json::from_value(tauri_utils::config::parse::read_from(parent.clone())?.0)?;
let (config, config_path) = tauri_utils::config::parse::read_from(parent.clone())?;
let mut config = serde_json::from_value(config)?;
if let Ok(env) = std::env::var("TAURI_CONFIG") {
let merge_config: serde_json::Value =
serde_json::from_str(&env).map_err(CodegenConfigError::FormatInline)?;
@ -84,5 +84,11 @@ pub fn get_config(path: &Path) -> Result<(Config, PathBuf), CodegenConfigError>
// Reset working directory.
std::env::set_current_dir(old_cwd).map_err(CodegenConfigError::CurrentDir)?;
Ok((config, parent))
Ok((
config,
config_path
.parent()
.map(ToOwned::to_owned)
.ok_or_else(|| CodegenConfigError::Parent(config_path))?,
))
}

View File

@ -6,28 +6,19 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::{config::Namespace, plugin::ManifestMap};
/// Resolved data associated with a member.
#[derive(Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct MemberResolution {
/// Member id.
pub member: String,
/// Resolved capabilities.
pub capabilities: HashMap<String, ResolvedCapability>,
}
/// A resolved capability.
#[derive(Default, Deserialize, Serialize)]
pub struct ResolvedCapability {
/// List of features enabled.
pub features: Vec<String>,
/// List of commands enabled.
pub commands: Vec<String>,
}
/// Lock file of the namespaces configuration.
#[derive(Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct NamespaceLockFile {
/// Lock file version.
pub version: u8,

View File

@ -118,7 +118,7 @@ impl Manifest {
}
/// A collection mapping a plugin name to its manifest.
#[derive(Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ManifestMap(HashMap<String, Manifest>);
impl From<HashMap<String, Manifest>> for ManifestMap {

View File

@ -66,6 +66,7 @@ pub use cocoa;
pub use embed_plist;
/// The Tauri error enum.
pub use error::Error;
use runtime_authority::RuntimeAuthority;
#[cfg(target_os = "ios")]
#[doc(hidden)]
pub use swift_rs;
@ -85,6 +86,7 @@ mod hooks;
mod manager;
mod pattern;
pub mod plugin;
pub mod runtime_authority;
mod vibrancy;
pub mod window;
use tauri_runtime as runtime;
@ -389,6 +391,7 @@ pub struct Context<A: Assets> {
pub(crate) package_info: PackageInfo,
pub(crate) _info_plist: (),
pub(crate) pattern: Pattern,
pub(crate) runtime_authority: RuntimeAuthority,
}
impl<A: Assets> fmt::Debug for Context<A> {
@ -487,6 +490,7 @@ impl<A: Assets> Context<A> {
package_info: PackageInfo,
info_plist: (),
pattern: Pattern,
runtime_authority: RuntimeAuthority,
) -> Self {
Self {
config,
@ -498,6 +502,7 @@ impl<A: Assets> Context<A> {
package_info,
_info_plist: info_plist,
pattern,
runtime_authority,
}
}

View File

@ -25,7 +25,6 @@ use tauri_utils::{
html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
};
use crate::app::{GlobalMenuEventListener, WindowMenuEvent};
use crate::hooks::IpcJavascript;
#[cfg(feature = "isolation")]
use crate::hooks::IsolationJavascript;
@ -51,6 +50,10 @@ use crate::{
Context, EventLoopMessage, Icon, Invoke, Manager, Pattern, Runtime, Scopes, StateManager, Window,
WindowEvent,
};
use crate::{
app::{GlobalMenuEventListener, WindowMenuEvent},
runtime_authority::RuntimeAuthority,
};
#[cfg(any(target_os = "linux", target_os = "windows"))]
use crate::path::BaseDirectory;
@ -234,6 +237,7 @@ pub struct InnerWindowManager<R: Runtime> {
invoke_initialization_script: String,
/// Application pattern.
pub(crate) pattern: Pattern,
pub(crate) runtime_authority: RuntimeAuthority,
}
impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
@ -334,6 +338,7 @@ impl<R: Runtime> WindowManager<R> {
window_event_listeners: Arc::new(window_event_listeners),
invoke_responder,
invoke_initialization_script,
runtime_authority: context.runtime_authority,
}),
}
}

View File

@ -0,0 +1,30 @@
//! Runtime authority.
pub use tauri_utils::namespace::MemberResolution;
/// The runtime authority verifies if a given IPC call is authorized.
#[derive(Default)]
pub struct RuntimeAuthority {
members: Vec<MemberResolution>,
}
impl RuntimeAuthority {
/// Creates the default (empty) runtime authority.
pub fn new() -> Self {
Self::default()
}
/// Adds the given member resolution to this authority.
pub fn add_member(&mut self, member: MemberResolution) {
self.members.push(member);
}
/// Determines if the given command is allowed for the member.
pub fn is_allowed(&self, member: &str, command: &String) -> bool {
if let Some(member) = self.members.iter().find(|m| m.member == member) {
member.commands.contains(command)
} else {
false
}
}
}

View File

@ -1691,6 +1691,15 @@ impl<R: Runtime> Window<R> {
return Ok(());
}
if !manager
.inner
.runtime_authority
.is_allowed(self.label(), &payload.cmd)
{
invoke.resolver.reject("Not allowed");
return Ok(());
}
if payload.cmd.starts_with("plugin:") {
if !is_local {
let command = invoke.message.command.replace("plugin:", "");

View File

@ -3,10 +3,11 @@
// SPDX-License-Identifier: MIT
fn main() {
tauri_build::build();
let mut codegen = tauri_build::CodegenContext::new();
if !cfg!(feature = "custom-protocol") {
codegen = codegen.dev();
}
codegen.build();
tauri_build::build();
}

View File

@ -48,13 +48,9 @@
"resolution": [
{
"member": "main",
"capabilities": {
"allow-ping": {
"features": [
"ping"
]
}
}
"commands": [
"plugin:sample|ping"
]
}
]
}