Skip to content

Commit

Permalink
feat: make mise trust act on directories instead of files (#3454)
Browse files Browse the repository at this point in the history
Fixes #3343
  • Loading branch information
jdx authored Dec 10, 2024
1 parent d21212a commit 7556fb0
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 40 deletions.
6 changes: 6 additions & 0 deletions e2e/config/test_config_ignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ assert "mise cfg" ""
assert "mise trust --all"
assert "mise cfg" "~/workdir/mise.toml dummy
~/workdir/subdir/mise.toml dummy"

rm -rf "$MISE_STATE_DIR"
export MISE_PARANOID=1
assert "mise trust --all"
assert "mise cfg" "~/workdir/mise.toml dummy
~/workdir/subdir/mise.toml dummy"
41 changes: 26 additions & 15 deletions src/cli/trust.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::path::PathBuf;

use crate::config::config_file::config_trust_root;
use crate::config::{
config_file, config_files_in_dir, ALL_CONFIG_FILES, DEFAULT_CONFIG_FILENAMES, SETTINGS,
config_file, config_files_in_dir, is_global_config, ALL_CONFIG_FILES, DEFAULT_CONFIG_FILENAMES,
SETTINGS,
};
use crate::file::{display_path, remove_file};
use crate::{config, dirs, env, file};
use clap::ValueHint;
use eyre::Result;
use itertools::Itertools;

/// Marks a config file as trusted
///
Expand Down Expand Up @@ -89,13 +92,14 @@ impl Trust {
}
},
};
config_file::untrust(&path)?;
let path = path.canonicalize()?;
info!("untrusted {}", path.display());
let cfr = config_trust_root(&path);
config_file::untrust(&cfr)?;
let cfr = cfr.canonicalize()?;
info!("untrusted {}", cfr.display());

let trusted_via_settings = SETTINGS.trusted_config_paths().any(|p| path.starts_with(p));
let trusted_via_settings = SETTINGS.trusted_config_paths().any(|p| cfr.starts_with(p));
if trusted_via_settings {
warn!("{path:?} is trusted via settings so it will still be trusted.");
warn!("{cfr:?} is trusted via settings so it will still be trusted.");
}

Ok(())
Expand All @@ -111,19 +115,20 @@ impl Trust {
}
},
};
config_file::add_ignored(path.clone())?;
let path = path.canonicalize()?;
info!("ignored {}", path.display());
let cfr = config_trust_root(&path);
config_file::add_ignored(cfr.clone())?;
let cfr = cfr.canonicalize()?;
info!("ignored {}", cfr.display());

let trusted_via_settings = SETTINGS.trusted_config_paths().any(|p| path.starts_with(p));
let trusted_via_settings = SETTINGS.trusted_config_paths().any(|p| cfr.starts_with(p));
if trusted_via_settings {
warn!("{path:?} is trusted via settings so it will still be trusted.");
warn!("{cfr:?} is trusted via settings so it will still be trusted.");
}
Ok(())
}
fn trust(&self) -> Result<()> {
let path = match self.config_file() {
Some(filename) => filename,
Some(filename) => config_trust_root(&filename),
None => match self.get_next_untrusted() {
Some(path) => path,
None => {
Expand All @@ -133,8 +138,8 @@ impl Trust {
},
};
config_file::trust(&path)?;
let path = path.canonicalize()?;
info!("trusted {}", path.display());
let cfr = path.canonicalize()?;
info!("trusted {}", cfr.display());
Ok(())
}

Expand All @@ -157,12 +162,18 @@ impl Trust {
fn get_next_untrusted(&self) -> Option<PathBuf> {
config::load_config_paths(&DEFAULT_CONFIG_FILENAMES, true)
.into_iter()
.find(|p| !config_file::is_trusted(p))
.filter(|p| !is_global_config(p))
.map(|p| config_trust_root(&p))
.unique()
.find(|ctr| !config_file::is_trusted(ctr))
}

fn show(&self) -> Result<()> {
let trusted = config::load_config_paths(&DEFAULT_CONFIG_FILENAMES, true)
.into_iter()
.filter(|p| !is_global_config(p))
.map(|p| config_trust_root(&p))
.unique()
.map(|p| (display_path(&p), config_file::is_trusted(&p)))
.rev()
.collect::<Vec<_>>();
Expand Down
4 changes: 2 additions & 2 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use versions::Versioning;

use crate::cli::args::{BackendArg, ToolVersionType};
use crate::config::config_file::toml::{deserialize_arr, deserialize_path_entry_arr};
use crate::config::config_file::{trust, trust_check, ConfigFile, TaskConfig};
use crate::config::config_file::{config_trust_root, trust, trust_check, ConfigFile, TaskConfig};
use crate::config::env_directive::{EnvDirective, PathEntry};
use crate::config::settings::SettingsPartial;
use crate::config::{Alias, AliasMap};
Expand Down Expand Up @@ -376,7 +376,7 @@ impl ConfigFile for MiseToml {
create_dir_all(parent)?;
}
file::write(&self.path, contents)?;
trust(&self.path)?;
trust(&config_trust_root(&self.path))?;
Ok(())
}

Expand Down
67 changes: 56 additions & 11 deletions src/config/config_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use eyre::{eyre, Result};
use idiomatic_version::IdiomaticVersionFile;
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use path_absolutize::Absolutize;
use serde_derive::Deserialize;
use versions::Versioning;

Expand All @@ -17,7 +18,7 @@ use tool_versions::ToolVersions;
use crate::cli::args::{BackendArg, ToolArg};
use crate::config::config_file::mise_toml::MiseToml;
use crate::config::env_directive::EnvDirective;
use crate::config::{AliasMap, Settings};
use crate::config::{settings, AliasMap, Settings, SETTINGS};
use crate::errors::Error::UntrustedConfig;
use crate::file::display_path;
use crate::hash::hash_to_str;
Expand Down Expand Up @@ -231,24 +232,66 @@ pub fn parse(path: &Path) -> eyre::Result<Box<dyn ConfigFile>> {
}
}

pub fn config_trust_root(path: &Path) -> PathBuf {
if settings::is_loaded() && SETTINGS.paranoid {
return path.to_path_buf();
}
let path = path
.absolutize()
.map(|p| p.to_path_buf())
.unwrap_or(path.to_path_buf());
let parts = path
.components()
.map(|c| c.as_os_str().to_string_lossy().to_string())
.collect::<Vec<_>>();
const EMPTY: &str = "";
// let filename = parts.last().map(|p| p.as_str()).unwrap_or(EMPTY);
let parent = parts
.get(parts.len() - 2)
.map(|p| p.as_str())
.unwrap_or(EMPTY);
let grandparent = parts
.get(parts.len() - 3)
.map(|p| p.as_str())
.unwrap_or(EMPTY);
let cur_path = || path.parent().unwrap().to_path_buf();
let parent_path = || cur_path().parent().unwrap().to_path_buf();
let grandparent_path = || parent_path().parent().unwrap().to_path_buf();
let is_mise_dir = |d: &str| d == "mise" || d == ".mise";
if parent == "conf.d" && is_mise_dir(grandparent) {
grandparent_path()
} else if is_mise_dir(parent) {
if grandparent == ".config" {
grandparent_path()
} else {
parent_path()
}
} else {
cur_path()
}
}

pub fn trust_check(path: &Path) -> eyre::Result<()> {
static MUTEX: Mutex<()> = Mutex::new(());
let _lock = MUTEX.lock().unwrap(); // Prevent multiple checks at once so we don't prompt multiple times for the same path
let config_root = config_trust_root(path);
let default_cmd = String::new();
let args = env::ARGS.read().unwrap();
let cmd = args.get(1).unwrap_or(&default_cmd).as_str();
if is_trusted(path) || cmd == "trust" || cfg!(test) {
if is_trusted(&config_root) || is_trusted(path) || cmd == "trust" || cfg!(test) {
return Ok(());
}
if cmd != "hook-env" && !is_ignored(path) {
if cmd != "hook-env" && !is_ignored(&config_root) && !is_ignored(path) {
let ans = prompt::confirm_with_all(format!(
"{} {} is not trusted. Trust it?",
"{} config files in {} are not trusted. Trust them?",
style::eyellow("mise"),
style::epath(path)
style::epath(&config_root)
))?;
if ans {
trust(path)?;
trust(&config_root)?;
return Ok(());
} else {
add_ignored(path.to_path_buf())?;
add_ignored(config_root.to_path_buf())?;
}
}
Err(UntrustedConfig(path.into()))?
Expand Down Expand Up @@ -349,16 +392,18 @@ pub fn is_ignored(path: &Path) -> bool {
}
}

pub fn trust(path: &Path) -> eyre::Result<()> {
pub fn trust(path: &Path) -> Result<()> {
rm_ignored(path.to_path_buf())?;
let hashed_path = trust_path(path);
if !hashed_path.exists() {
file::create_dir_all(hashed_path.parent().unwrap())?;
file::make_symlink_or_file(path.canonicalize()?.as_path(), &hashed_path)?;
}
let trust_hash_path = hashed_path.with_extension("hash");
let hash = hash::file_hash_sha256(path, None)?;
file::write(trust_hash_path, hash)?;
if SETTINGS.paranoid {
let trust_hash_path = hashed_path.with_extension("hash");
let hash = hash::file_hash_sha256(path, None)?;
file::write(trust_hash_path, hash)?;
}
Ok(())
}

Expand Down
7 changes: 1 addition & 6 deletions src/config/config_file/tool_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use itertools::Itertools;
use tera::Context;

use crate::cli::args::BackendArg;
use crate::config::config_file;
use crate::config::config_file::ConfigFile;
use crate::file;
use crate::file::display_path;
Expand Down Expand Up @@ -56,11 +55,7 @@ impl ToolVersions {
pub fn parse_str(s: &str, path: PathBuf) -> Result<Self> {
let mut cf = Self::init(&path);
let dir = path.parent();
let s = if config_file::is_trusted(&path) {
get_tera(dir).render_str(s, &cf.context)?
} else {
s.to_string()
};
let s = get_tera(dir).render_str(s, &cf.context)?;
for line in s.lines() {
if !line.trim_start().starts_with('#') {
break;
Expand Down
4 changes: 2 additions & 2 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::backend::ABackend;
use crate::cli::version;
use crate::config::config_file::idiomatic_version::IdiomaticVersionFile;
use crate::config::config_file::mise_toml::{MiseToml, Tasks};
use crate::config::config_file::ConfigFile;
use crate::config::config_file::{config_trust_root, ConfigFile};
use crate::config::env_directive::EnvResults;
use crate::config::tracking::Tracker;
use crate::file::display_path;
Expand Down Expand Up @@ -781,7 +781,7 @@ pub fn load_config_paths(config_filenames: &[String], include_ignored: bool) ->
config_files
.into_iter()
.unique_by(|p| file::desymlink_path(p))
.filter(|p| include_ignored || !config_file::is_ignored(p))
.filter(|p| include_ignored || !config_file::is_ignored(&config_trust_root(p)))
.collect()
}

Expand Down
6 changes: 3 additions & 3 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ static DEFAULT_SETTINGS: Lazy<SettingsPartial> = Lazy::new(|| {
s
});

// pub fn is_loaded() -> bool {
// BASE_SETTINGS.read().unwrap().is_some()
// }
pub fn is_loaded() -> bool {
BASE_SETTINGS.read().unwrap().is_some()
}

#[derive(Serialize, Deserialize)]
pub struct SettingsFile {
Expand Down
2 changes: 1 addition & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub enum Error {
#[error("{} exited with non-zero status: {}", .0, render_exit_status(.1))]
ScriptFailed(String, Option<ExitStatus>),
#[error(
"Config file {} is not trusted.\nTrust it with `mise trust`.",
"Config files in {} are not trusted.\nTrust them with `mise trust`.",
display_path(.0)
)]
UntrustedConfig(PathBuf),
Expand Down

0 comments on commit 7556fb0

Please sign in to comment.