Skip to content

Commit

Permalink
feat(core): shell execute API scope [TRI-002] (#36)
Browse files Browse the repository at this point in the history
* feat(core): shell execute API scope [TRI-002]

* fix tests

* also check with empty extension

* lockfile
  • Loading branch information
lucasfernog committed Jan 9, 2022
1 parent eae311e commit d4db95e
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 274 deletions.
1 change: 1 addition & 0 deletions .changes/scopes.md
Expand Up @@ -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.
19 changes: 16 additions & 3 deletions core/tauri-utils/src/config.rs
Expand Up @@ -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`,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions core/tauri/src/app.rs
Expand Up @@ -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()
}
}
};
}
Expand Down Expand Up @@ -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);

Expand Down
37 changes: 32 additions & 5 deletions core/tauri/src/endpoints/shell.rs
Expand Up @@ -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};

Expand Down Expand Up @@ -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)]
Expand All @@ -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,
Expand All @@ -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))]
{
Expand Down Expand Up @@ -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};

Expand All @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions core/tauri/src/error.rs
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions core/tauri/src/scope/mod.rs
Expand Up @@ -4,16 +4,16 @@

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,
#[cfg(protocol_asset)]
pub asset_protocol: FsScope,
#[cfg(http_request)]
pub http: HttpScope,
#[cfg(shell_execute)]
pub shell: FsScope,
}
6 changes: 0 additions & 6 deletions core/tauri/src/scope/shell.rs

This file was deleted.

2 changes: 1 addition & 1 deletion core/tauri/tests/restart/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d4db95e

Please sign in to comment.