Просмотр исходного кода

feat(core): shell execute API scope [TRI-002] (#36)

* feat(core): shell execute API scope [TRI-002]

* fix tests

* also check with empty extension

* lockfile
Lucas Fernandes Nogueira 3 лет назад
Родитель
Сommit
d4db95e716

+ 1 - 0
.changes/scopes.md

@@ -5,3 +5,4 @@
 Scopes the `filesystem` APIs from the webview access using `tauri.conf.json > tauri > allowlist > fs > scope`.
 Scopes the `asset` protocol access using `tauri.conf.json > tauri > allowlist > protocol > assetScope`.
 Scopes the `http` APIs from the webview access using `tauri.conf.json > tauri > allowlist > http > scope`.
+Scopes the `shell` execute API from the webview access using `tauri.conf.json > tauri > allowlist > shell > scope`. Additionally, check the `tauri.conf.json > tauri > bundle > externalBin` to prevent access to unknown sidecars.

+ 16 - 3
core/tauri-utils/src/config.rs

@@ -575,8 +575,8 @@ macro_rules! check_feature {
   };
 }
 
-/// Filesystem API scope definition.
-/// It is a list of glob patterns that restrict the filesystem API access from the webview.
+/// Filesystem scope definition.
+/// It is a list of glob patterns that restrict the API access from the webview.
 /// Each pattern can start with a variable that resolves to a system base directory.
 /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
 /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
@@ -830,6 +830,10 @@ impl Allowlist for WindowAllowlistConfig {
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct ShellAllowlistConfig {
+  /// Access scope for the binary execution APIs.
+  /// Sidecars are automatically enabled.
+  #[serde(default)]
+  pub scope: FsAllowlistScope,
   /// Use this flag to enable all shell API features.
   #[serde(default)]
   pub all: bool,
@@ -849,6 +853,7 @@ pub struct ShellAllowlistConfig {
 impl Allowlist for ShellAllowlistConfig {
   fn all_features() -> Vec<&'static str> {
     let allowlist = Self {
+      scope: Default::default(),
       all: false,
       execute: true,
       sidecar: true,
@@ -939,6 +944,7 @@ pub struct HttpAllowlistScope(pub Vec<Url>);
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct HttpAllowlistConfig {
   /// The access scope for the HTTP APIs.
+  #[serde(default)]
   pub scope: HttpAllowlistScope,
   /// Use this flag to enable all HTTP API features.
   #[serde(default)]
@@ -1955,7 +1961,7 @@ mod build {
       let long_description = quote!(None);
       let deb = quote!(Default::default());
       let macos = quote!(Default::default());
-      let external_bin = quote!(None);
+      let external_bin = opt_vec_str_lit(self.external_bin.as_ref());
       let windows = &self.windows;
 
       literal_struct!(
@@ -2081,6 +2087,13 @@ mod build {
     }
   }
 
+  impl ToTokens for ShellAllowlistConfig {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+      let scope = &self.scope;
+      tokens.append_all(quote! { ::tauri::utils::config::ShellAllowlistConfig { scope: #scope, ..Default::default() } })
+    }
+  }
+
   impl ToTokens for AllowlistConfig {
     fn to_tokens(&self, tokens: &mut TokenStream) {
       let fs = &self.fs;

+ 13 - 0
core/tauri/src/app.rs

@@ -458,6 +458,12 @@ macro_rules! shared_app_impl {
       pub fn asset_protocol_scope(&self) -> FsScope {
         self.state::<Scopes>().inner().asset_protocol.clone()
       }
+
+      /// Gets the scope for the shell execute APIs.
+      #[cfg(shell_execute)]
+      pub fn shell_scope(&self) -> FsScope {
+        self.state::<Scopes>().inner().shell.clone()
+      }
     }
   };
 }
@@ -1027,6 +1033,13 @@ impl<R: Runtime> Builder<R> {
       ),
       #[cfg(http_request)]
       http: crate::scope::HttpScope::for_http_api(&app.config().tauri.allowlist.http.scope),
+      #[cfg(shell_execute)]
+      shell: FsScope::for_fs_api(
+        &app.manager.config(),
+        app.package_info(),
+        &env,
+        &app.config().tauri.allowlist.shell.scope,
+      ),
     });
     app.manage(env);
 

+ 32 - 5
core/tauri/src/endpoints/shell.rs

@@ -4,6 +4,8 @@
 
 use super::InvokeContext;
 use crate::{api::ipc::CallbackFn, Runtime};
+#[cfg(shell_execute)]
+use crate::{Manager, Scopes};
 use serde::Deserialize;
 use tauri_macros::{module_command_handler, CommandModule};
 
@@ -54,7 +56,7 @@ pub enum Cmd {
   /// The execute script API.
   #[serde(rename_all = "camelCase")]
   Execute {
-    program: String,
+    program: PathBuf,
     args: Vec<String>,
     on_event_fn: CallbackFn,
     #[serde(default)]
@@ -77,7 +79,7 @@ impl Cmd {
   #[allow(unused_variables)]
   fn execute<R: Runtime>(
     context: InvokeContext<R>,
-    program: String,
+    program: PathBuf,
     args: Vec<String>,
     on_event_fn: CallbackFn,
     options: CommandOptions,
@@ -88,14 +90,38 @@ impl Cmd {
         "shell > sidecar".to_string(),
       ));
       #[cfg(shell_sidecar)]
-      crate::api::process::Command::new_sidecar(program)?
+      {
+        let program_as_string = program.display().to_string();
+        let program_no_ext_as_string = program.with_extension("").display().to_string();
+        let is_configured = context
+          .config
+          .tauri
+          .bundle
+          .external_bin
+          .as_ref()
+          .map(|bins| {
+            bins
+              .iter()
+              .any(|b| b == &program_as_string || b == program_no_ext_as_string)
+          })
+          .unwrap_or_default();
+        if is_configured {
+          crate::api::process::Command::new_sidecar(program_as_string)?
+        } else {
+          return Err(crate::Error::SidecarNotAllowed(program));
+        }
+      }
     } else {
       #[cfg(not(shell_execute))]
       return Err(crate::Error::ApiNotAllowlisted(
         "shell > execute".to_string(),
       ));
       #[cfg(shell_execute)]
-      crate::api::process::Command::new(program)
+      if context.window.state::<Scopes>().shell.is_allowed(&program) {
+        crate::api::process::Command::new(program.display().to_string())
+      } else {
+        return Err(crate::Error::ProgramNotAllowed(program));
+      }
     };
     #[cfg(any(shell_execute, shell_sidecar))]
     {
@@ -195,6 +221,7 @@ impl Cmd {
 mod tests {
   use super::{Buffer, ChildId, CommandOptions};
   use crate::api::ipc::CallbackFn;
+  use std::path::PathBuf;
 
   use quickcheck::{Arbitrary, Gen};
 
@@ -217,7 +244,7 @@ mod tests {
   #[tauri_macros::module_command_test(shell_execute, "shell > execute")]
   #[quickcheck_macros::quickcheck]
   fn execute(
-    _program: String,
+    _program: PathBuf,
     _args: Vec<String>,
     _on_event_fn: CallbackFn,
     _options: CommandOptions,

+ 6 - 0
core/tauri/src/error.rs

@@ -90,6 +90,12 @@ pub enum Error {
   /// URL not allowed by the scope.
   #[error("url not allowed on the configured scope: {0}")]
   UrlNotAllowed(url::Url),
+  /// Sidecar not allowed by the configuration.
+  #[error("sidecar not configured under `tauri.conf.json > tauri > bundle > externalBin`: {0}")]
+  SidecarNotAllowed(PathBuf),
+  /// Program not allowed by the scope.
+  #[error("program not allowed on the configured shell scope: {0}")]
+  ProgramNotAllowed(PathBuf),
 }
 
 impl From<serde_json::Error> for Error {

+ 2 - 2
core/tauri/src/scope/mod.rs

@@ -4,11 +4,9 @@
 
 mod fs;
 mod http;
-mod shell;
 
 pub use self::http::Scope as HttpScope;
 pub use fs::Scope as FsScope;
-pub use shell::Scope as ShellScope;
 
 pub(crate) struct Scopes {
   pub fs: FsScope,
@@ -16,4 +14,6 @@ pub(crate) struct Scopes {
   pub asset_protocol: FsScope,
   #[cfg(http_request)]
   pub http: HttpScope,
+  #[cfg(shell_execute)]
+  pub shell: FsScope,
 }

+ 0 - 6
core/tauri/src/scope/shell.rs

@@ -1,6 +0,0 @@
-// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-License-Identifier: MIT
-
-/// Scope for the shell API access.
-pub struct Scope();

+ 1 - 1
core/tauri/tests/restart/Cargo.lock

@@ -3107,7 +3107,7 @@ checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab"
 [[package]]
 name = "wry"
 version = "0.12.2"
-source = "git+https://github.com/tauri-sec/wry?branch=next#0f9eb9b1b9288b5be443bced0e8fa58a2479c491"
+source = "git+ssh://git@github.com/tauri-sec/wry?branch=next#0f9eb9b1b9288b5be443bced0e8fa58a2479c491"
 dependencies = [
  "cocoa",
  "core-graphics 0.22.3",

+ 1 - 253
examples/sidecar/src-tauri/Cargo.lock

@@ -74,24 +74,6 @@ dependencies = [
  "system-deps 3.2.0",
 ]
 
-[[package]]
-name = "attohttpc"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e69e13a99a7e6e070bb114f7ff381e58c7ccc188630121fc4c2fe4bcf24cd072"
-dependencies = [
- "flate2",
- "http",
- "log",
- "native-tls",
- "openssl",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "url",
- "wildmatch",
-]
-
 [[package]]
 name = "autocfg"
 version = "1.0.1"
@@ -158,12 +140,6 @@ dependencies = [
  "memchr",
 ]
 
-[[package]]
-name = "bumpalo"
-version = "3.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
-
 [[package]]
 name = "byteorder"
 version = "1.4.3"
@@ -1378,15 +1354,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "js-sys"
-version = "0.3.55"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
-dependencies = [
- "wasm-bindgen",
-]
-
 [[package]]
 name = "kuchiki"
 version = "0.8.1"
@@ -1522,24 +1489,6 @@ dependencies = [
  "autocfg",
 ]
 
-[[package]]
-name = "native-tls"
-version = "0.2.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
-dependencies = [
- "lazy_static",
- "libc",
- "log",
- "openssl",
- "openssl-probe",
- "openssl-sys",
- "schannel",
- "security-framework",
- "security-framework-sys",
- "tempfile",
-]
-
 [[package]]
 name = "ndk"
 version = "0.4.0"
@@ -1670,17 +1619,6 @@ dependencies = [
  "objc_exception",
 ]
 
-[[package]]
-name = "objc-foundation"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
-dependencies = [
- "block",
- "objc",
- "objc_id",
-]
-
 [[package]]
 name = "objc_exception"
 version = "0.1.2"
@@ -1711,39 +1649,6 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
-[[package]]
-name = "openssl"
-version = "0.10.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a"
-dependencies = [
- "bitflags",
- "cfg-if 1.0.0",
- "foreign-types",
- "libc",
- "once_cell",
- "openssl-sys",
-]
-
-[[package]]
-name = "openssl-probe"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
-
-[[package]]
-name = "openssl-sys"
-version = "0.9.67"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058"
-dependencies = [
- "autocfg",
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
-
 [[package]]
 name = "os_pipe"
 version = "0.9.2"
@@ -2229,29 +2134,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "rfd"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2acac5884e3a23b02ebd6ce50fd2729732cdbdb16ea944fbbfbfa638a67992aa"
-dependencies = [
- "block",
- "dispatch",
- "glib-sys 0.14.0",
- "gobject-sys 0.14.0",
- "gtk-sys",
- "js-sys",
- "lazy_static",
- "objc",
- "objc-foundation",
- "objc_id",
- "raw-window-handle",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
- "winapi",
-]
-
 [[package]]
 name = "rustc_version"
 version = "0.3.3"
@@ -2282,16 +2164,6 @@ dependencies = [
  "winapi-util",
 ]
 
-[[package]]
-name = "schannel"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
-dependencies = [
- "lazy_static",
- "winapi",
-]
-
 [[package]]
 name = "scoped-tls"
 version = "1.0.0"
@@ -2304,29 +2176,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
-[[package]]
-name = "security-framework"
-version = "2.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
-dependencies = [
- "bitflags",
- "core-foundation 0.9.2",
- "core-foundation-sys 0.8.3",
- "libc",
- "security-framework-sys",
-]
-
-[[package]]
-name = "security-framework-sys"
-version = "2.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
-dependencies = [
- "core-foundation-sys 0.8.3",
- "libc",
-]
-
 [[package]]
 name = "selectors"
 version = "0.22.0"
@@ -2413,18 +2262,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "serde_urlencoded"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
- "serde",
-]
-
 [[package]]
 name = "serde_with"
 version = "1.11.0"
@@ -2733,7 +2570,6 @@ dependencies = [
 name = "tauri"
 version = "1.0.0-beta.8"
 dependencies = [
- "attohttpc",
  "bincode",
  "cfg_aliases",
  "data-url",
@@ -2754,7 +2590,6 @@ dependencies = [
  "rand 0.8.4",
  "raw-window-handle",
  "regex",
- "rfd",
  "semver 1.0.4",
  "serde",
  "serde_json",
@@ -2809,6 +2644,7 @@ dependencies = [
 name = "tauri-macros"
 version = "1.0.0-beta.5"
 dependencies = [
+ "heck",
  "proc-macro2",
  "quote",
  "syn",
@@ -3113,12 +2949,6 @@ dependencies = [
  "getrandom 0.2.3",
 ]
 
-[[package]]
-name = "vcpkg"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
-
 [[package]]
 name = "version-compare"
 version = "0.0.10"
@@ -3166,82 +2996,6 @@ version = "0.10.2+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
 
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.78"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
-dependencies = [
- "cfg-if 1.0.0",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.78"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
-dependencies = [
- "bumpalo",
- "lazy_static",
- "log",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-futures"
-version = "0.4.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
-dependencies = [
- "cfg-if 1.0.0",
- "js-sys",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.78"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.78"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.78"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
-
-[[package]]
-name = "web-sys"
-version = "0.3.55"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
 [[package]]
 name = "webkit2gtk"
 version = "0.15.1"
@@ -3323,12 +3077,6 @@ dependencies = [
  "windows",
 ]
 
-[[package]]
-name = "wildmatch"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0"
-
 [[package]]
 name = "winapi"
 version = "0.3.9"

+ 22 - 4
tooling/cli.rs/schema.json

@@ -103,6 +103,9 @@
             "all": false,
             "execute": false,
             "open": false,
+            "scope": [
+              "$APP/**"
+            ],
             "sidecar": false
           },
           "window": {
@@ -339,6 +342,9 @@
             "all": false,
             "execute": false,
             "open": false,
+            "scope": [
+              "$APP/**"
+            ],
             "sidecar": false
           },
           "allOf": [
@@ -1013,7 +1019,7 @@
       "additionalProperties": false
     },
     "FsAllowlistScope": {
-      "description": "Filesystem API scope definition. It is a list of glob patterns that restrict the filesystem API access from the webview. Each pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$CWD`.",
+      "description": "Filesystem scope definition. It is a list of glob patterns that restrict the API access from the webview. Each pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$CWD`.",
       "type": "array",
       "items": {
         "type": "string"
@@ -1034,9 +1040,6 @@
     "HttpAllowlistConfig": {
       "description": "Allowlist for the HTTP APIs.",
       "type": "object",
-      "required": [
-        "scope"
-      ],
       "properties": {
         "all": {
           "description": "Use this flag to enable all HTTP API features.",
@@ -1050,6 +1053,7 @@
         },
         "scope": {
           "description": "The access scope for the HTTP APIs.",
+          "default": [],
           "allOf": [
             {
               "$ref": "#/definitions/HttpAllowlistScope"
@@ -1277,6 +1281,17 @@
           "default": false,
           "type": "boolean"
         },
+        "scope": {
+          "description": "Access scope for the binary execution APIs. Sidecars are automatically enabled.",
+          "default": [
+            "$APP/**"
+          ],
+          "allOf": [
+            {
+              "$ref": "#/definitions/FsAllowlistScope"
+            }
+          ]
+        },
         "sidecar": {
           "description": "Enable sidecar execution, allowing the JavaScript layer to spawn a sidecar program, an executable that is shipped with the application. For more information see <https://tauri.studio/en/docs/usage/guides/bundler/sidecar>.",
           "default": false,
@@ -1372,6 +1387,9 @@
               "all": false,
               "execute": false,
               "open": false,
+              "scope": [
+                "$APP/**"
+              ],
               "sidecar": false
             },
             "window": {