Parcourir la source

initial draft for runtime authority

Lucas Nogueira il y a 2 ans
Parent
commit
708cb9fa28

+ 8 - 6
core/tauri-build/src/lib.rs

@@ -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<_>>(),
+        );
       }
     }
   }

+ 29 - 1
core/tauri-codegen/src/context.rs

@@ -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,

+ 6 - 0
core/tauri-codegen/src/embedded_assets.rs

@@ -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.

+ 10 - 4
core/tauri-codegen/src/lib.rs

@@ -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))?,
+  ))
 }

+ 4 - 13
core/tauri-utils/src/namespace.rs

@@ -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,

+ 1 - 1
core/tauri-utils/src/plugin.rs

@@ -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 {

+ 5 - 0
core/tauri/src/lib.rs

@@ -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,
     }
   }
 

+ 6 - 1
core/tauri/src/manager.rs

@@ -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,
       }),
     }
   }

+ 30 - 0
core/tauri/src/runtime_authority.rs

@@ -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
+    }
+  }
+}

+ 9 - 0
core/tauri/src/window.rs

@@ -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:", "");

+ 2 - 1
examples/api/src-tauri/build.rs

@@ -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();
 }

+ 3 - 7
examples/api/src-tauri/tauri.namespace.lock

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