fs_mistrust/disable.rs
1//! Functionality for disabling `fs-mistrust` checks based on configuration or
2//! environment variables.
3
4use std::env::{self, VarError};
5
6/// Convenience type to indicate whether permission checks are disabled.
7///
8/// Used to avoid accidents with boolean meanings.
9#[derive(Copy, Clone, Eq, PartialEq, Debug)]
10pub(crate) enum Status {
11 /// We should indeed run permission checks, and treat some users as untrusted.
12 CheckPermissions,
13 /// We should treat every user as trusted, and therefore disable (most)
14 /// permissions checks.
15 DisableChecks,
16}
17
18impl Status {
19 /// Return true if this `Status` tells us to disable checks.
20 pub(crate) fn disabled(self) -> bool {
21 self == Status::DisableChecks
22 }
23}
24
25/// An environment variable which, if set, will cause a us to trust all users
26/// (and therefore, in effect, to disable all permissions checks.)
27pub const GLOBAL_DISABLE_VAR: &str = "FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS";
28
29/// Value to configure when permission checks should be disabled. This type is
30/// set in the builder, and converted to a bool in the `Mistrust`.
31#[derive(Clone, Default, Debug, Eq, PartialEq)]
32pub(crate) enum Disable {
33 /// Check a caller-provided environment variable, and honor it if it is set.
34 /// If it is not set, fall back to checking
35 /// `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS`.
36 OnUserEnvVar(String),
37 /// Disable permissions checks if the value of
38 /// `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS` is something other than "false",
39 /// "0", "no", etc..
40 ///
41 /// This is the default.
42 #[default]
43 OnGlobalEnvVar,
44 /// Perform permissions checks regardless of any values in the environment.
45 Never,
46}
47
48/// Convert the result of `std::env::var` to a boolean, if the variable is set.
49///
50/// Names that seem to say "don't disable" are treated as `Some(false)`. Any
51/// other value is treated as `Some(true)`. (That is, we err on the side of
52/// assuming that if you set a disable variable, you meant to disable.)
53///
54/// Absent environment vars, or those set to the empty string, are treated as
55/// None.
56#[allow(clippy::match_like_matches_macro)]
57fn from_env_var_value(input: std::result::Result<String, VarError>) -> Option<Status> {
58 // WARNING: This behaviour of the environment variable parsing/evaluation is considered
59 // stable and should not be modified unless necessary.
60 // This behaviour is part of the public interface of applications which use fs-mistrust,
61 // so changing the behaviour of this function may result in a breaking change for applications.
62
63 let mut s = match input {
64 Ok(s) => s,
65 Err(VarError::NotPresent) => return None,
66 Err(VarError::NotUnicode(_)) => return Some(Status::DisableChecks),
67 };
68
69 s.make_ascii_lowercase();
70 let s = s.trim();
71
72 match s {
73 "" => None,
74 "0" | "no" | "never" | "false" | "n" => Some(Status::CheckPermissions),
75 _ => Some(Status::DisableChecks),
76 }
77}
78
79/// As `from_env_value`, but takes the name of the variable.
80fn from_env_var(varname: &str) -> Option<Status> {
81 from_env_var_value(env::var(varname))
82}
83
84impl Disable {
85 /// Return true if, based on this [`Disable`] setting, and on the
86 /// environment, we should disable permissions checking.
87 pub(crate) fn should_disable_checks(&self) -> Status {
88 match self {
89 Disable::OnUserEnvVar(varname) => from_env_var(varname)
90 .or_else(|| from_env_var(GLOBAL_DISABLE_VAR))
91 .unwrap_or(Status::CheckPermissions),
92 Disable::OnGlobalEnvVar => {
93 from_env_var(GLOBAL_DISABLE_VAR).unwrap_or(Status::CheckPermissions)
94 }
95 Disable::Never => Status::CheckPermissions,
96 }
97 }
98}
99
100#[cfg(test)]
101mod test {
102 // @@ begin test lint list maintained by maint/add_warning @@
103 #![allow(clippy::bool_assert_comparison)]
104 #![allow(clippy::clone_on_copy)]
105 #![allow(clippy::dbg_macro)]
106 #![allow(clippy::mixed_attributes_style)]
107 #![allow(clippy::print_stderr)]
108 #![allow(clippy::print_stdout)]
109 #![allow(clippy::single_char_pattern)]
110 #![allow(clippy::unwrap_used)]
111 #![allow(clippy::unchecked_duration_subtraction)]
112 #![allow(clippy::useless_vec)]
113 #![allow(clippy::needless_pass_by_value)]
114 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
115 use super::*;
116 #[test]
117 fn from_val() {
118 for word in ["yes", "1", "true", "certainly", "whatever"] {
119 assert_eq!(
120 from_env_var_value(Ok(word.into())),
121 Some(Status::DisableChecks)
122 );
123 }
124
125 for word in ["no", "0", "false", "NO", "Never", "n"] {
126 assert_eq!(
127 from_env_var_value(Ok(word.into())),
128 Some(Status::CheckPermissions)
129 );
130 }
131
132 assert_eq!(from_env_var_value(Ok("".into())), None);
133 assert_eq!(from_env_var_value(Ok(" ".into())), None);
134
135 assert_eq!(from_env_var_value(Err(VarError::NotPresent)), None);
136 assert_eq!(
137 from_env_var_value(Err(VarError::NotUnicode("".into()))),
138 Some(Status::DisableChecks)
139 );
140
141 // see https://gitlab.torproject.org/tpo/core/arti/-/issues/1782
142 assert_eq!(
143 from_env_var_value(Ok(" false ".to_string())),
144 Some(Status::CheckPermissions),
145 );
146 }
147}