From 7556fb0c5a4d144dd3afd614e3df0a8373ac5f62 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:46:42 -0600 Subject: [PATCH] feat: make `mise trust` act on directories instead of files (#3454) Fixes #3343 --- e2e/config/test_config_ignore | 6 +++ src/cli/trust.rs | 41 +++++++++------ src/config/config_file/mise_toml.rs | 4 +- src/config/config_file/mod.rs | 67 +++++++++++++++++++++---- src/config/config_file/tool_versions.rs | 7 +-- src/config/mod.rs | 4 +- src/config/settings.rs | 6 +-- src/errors.rs | 2 +- 8 files changed, 97 insertions(+), 40 deletions(-) diff --git a/e2e/config/test_config_ignore b/e2e/config/test_config_ignore index 7a35ba0204..d2debd42bc 100644 --- a/e2e/config/test_config_ignore +++ b/e2e/config/test_config_ignore @@ -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" diff --git a/src/cli/trust.rs b/src/cli/trust.rs index 539972ad86..b466209001 100644 --- a/src/cli/trust.rs +++ b/src/cli/trust.rs @@ -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 /// @@ -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(()) @@ -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 => { @@ -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(()) } @@ -157,12 +162,18 @@ impl Trust { fn get_next_untrusted(&self) -> Option { 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::>(); diff --git a/src/config/config_file/mise_toml.rs b/src/config/config_file/mise_toml.rs index c97aaabe2a..011d0b1696 100644 --- a/src/config/config_file/mise_toml.rs +++ b/src/config/config_file/mise_toml.rs @@ -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}; @@ -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(()) } diff --git a/src/config/config_file/mod.rs b/src/config/config_file/mod.rs index c2e30dd4ce..a18a627624 100644 --- a/src/config/config_file/mod.rs +++ b/src/config/config_file/mod.rs @@ -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; @@ -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; @@ -231,24 +232,66 @@ pub fn parse(path: &Path) -> eyre::Result> { } } +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::>(); + 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()))? @@ -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(()) } diff --git a/src/config/config_file/tool_versions.rs b/src/config/config_file/tool_versions.rs index 88008ccb68..591245bbdf 100644 --- a/src/config/config_file/tool_versions.rs +++ b/src/config/config_file/tool_versions.rs @@ -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; @@ -56,11 +55,7 @@ impl ToolVersions { pub fn parse_str(s: &str, path: PathBuf) -> Result { 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; diff --git a/src/config/mod.rs b/src/config/mod.rs index 64848f09f1..2293f8bc2f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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; @@ -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() } diff --git a/src/config/settings.rs b/src/config/settings.rs index 2eeee2e92c..410cb7b63c 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -72,9 +72,9 @@ static DEFAULT_SETTINGS: Lazy = 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 { diff --git a/src/errors.rs b/src/errors.rs index 325508d74e..8b17233935 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -22,7 +22,7 @@ pub enum Error { #[error("{} exited with non-zero status: {}", .0, render_exit_status(.1))] ScriptFailed(String, Option), #[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),