1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! A shared trust model for `gitoxide` crates.
//!
//! ## Feature Flags
#![cfg_attr(
    feature = "document-features",
    cfg_attr(doc, doc = ::document_features::document_features!())
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
// `unsafe_code` not forbidden because we need to interact with the libc
#![deny(missing_docs, rust_2018_idioms, unsafe_code)]

use std::{
    fmt::{Debug, Display, Formatter},
    marker::PhantomData,
    ops::Deref,
};

/// A way to specify how 'safe' we feel about a resource, typically about a git repository.
#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Trust {
    /// Caution is warranted when using the resource.
    Reduced,
    /// We have no doubts that this resource means no harm and it can be used at will.
    Full,
}

///
pub mod trust {
    use crate::Trust;

    impl Trust {
        /// Derive `Full` trust if `path` is owned by the user executing the current process, or `Reduced` trust otherwise.
        pub fn from_path_ownership(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
            Ok(crate::identity::is_path_owned_by_current_user(path.as_ref())?
                .then(|| Trust::Full)
                .unwrap_or(Trust::Reduced))
        }
    }

    /// A trait to help creating default values based on a trust level.
    pub trait DefaultForLevel {
        /// Produce a default value for the given trust `level`.
        fn default_for_level(level: Trust) -> Self;
    }

    /// Associate instructions for how to deal with various `Trust` levels as they are encountered in the wild.
    pub struct Mapping<T> {
        /// The value for fully trusted resources.
        pub full: T,
        /// The value for resources with reduced trust.
        pub reduced: T,
    }

    impl<T> Default for Mapping<T>
    where
        T: DefaultForLevel,
    {
        fn default() -> Self {
            Mapping {
                full: T::default_for_level(Trust::Full),
                reduced: T::default_for_level(Trust::Reduced),
            }
        }
    }

    impl<T> Mapping<T> {
        /// Obtain the value for the given trust `level`.
        pub fn by_level(&self, level: Trust) -> &T {
            match level {
                Trust::Full => &self.full,
                Trust::Reduced => &self.reduced,
            }
        }

        /// Obtain the value for the given `level` once.
        pub fn into_value_by_level(self, level: Trust) -> T {
            match level {
                Trust::Full => self.full,
                Trust::Reduced => self.reduced,
            }
        }
    }
}

///
pub mod permission {
    use std::fmt::{Debug, Display};

    use crate::Access;

    /// A marker trait to signal tags for permissions.
    pub trait Tag: Debug + Clone {}

    /// A tag indicating that a permission is applying to the contents of a configuration file.
    #[derive(Debug, Clone)]
    pub struct Config;
    impl Tag for Config {}

    /// A tag indicating that a permission is applying to the resource itself.
    #[derive(Debug, Clone)]
    pub struct Resource;
    impl Tag for Resource {}

    impl<P: Debug + Display + Clone> Access<Config, P> {
        /// Create a permission for values contained in git configuration files.
        ///
        /// This applies permissions to values contained inside of these files.
        pub fn config(permission: P) -> Self {
            Access {
                permission,
                _data: Default::default(),
            }
        }
    }

    impl<P: Debug + Display + Clone> Access<Resource, P> {
        /// Create a permission a file or directory itself.
        ///
        /// This applies permissions to a configuration file itself and whether it can be used at all, or to a directory
        /// to read from or write to.
        pub fn resource(permission: P) -> Self {
            Access {
                permission,
                _data: Default::default(),
            }
        }
    }

    /// An error to use if an operation cannot proceed due to insufficient permissions.
    ///
    /// It's up to the implementation to decide which permission is required for an operation, and which one
    /// causes errors.
    #[cfg(feature = "thiserror")]
    #[derive(Debug, thiserror::Error)]
    #[error("Not allowed to handle resource {:?}: permission {}", .resource, .permission)]
    pub struct Error<R: Debug, P: Debug + Display> {
        /// The resource which cannot be used.
        pub resource: R,
        /// The permission causing it to be disallowed.
        pub permission: P,
    }
}

/// Allow, deny or forbid using a resource or performing an action.
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Permission {
    /// Fail outright when trying to load a resource or performing an action.
    Forbid,
    /// Ignore resources or try to avoid performing an operation.
    Deny,
    /// Allow loading a resource or performing an action.
    Allow,
}

impl Permission {
    /// Check this permissions and produce a reply to indicate if the `resource` can be used and in which way.
    ///
    /// Only if this permission is set to `Allow` will the resource be usable.
    #[cfg(feature = "thiserror")]
    pub fn check<R: Debug>(&self, resource: R) -> Result<Option<R>, permission::Error<R, Self>> {
        match self {
            Permission::Allow => Ok(Some(resource)),
            Permission::Deny => Ok(None),
            Permission::Forbid => Err(permission::Error {
                resource,
                permission: *self,
            }),
        }
    }
}

impl Display for Permission {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(
            match self {
                Permission::Allow => "allowed",
                Permission::Deny => "denied",
                Permission::Forbid => "forbidden",
            },
            f,
        )
    }
}

bitflags::bitflags! {
    /// Whether something can be read or written.
    #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
    pub struct ReadWrite: u8 {
        /// The item can be read.
        const READ = 1 << 0;
        /// The item can be written
        const WRITE = 1 << 1;
    }
}

impl Display for ReadWrite {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

/// A container to define tagged access permissions, rendering the permission read-only.
#[derive(Debug, Clone)]
pub struct Access<T: permission::Tag, P: Debug + Display + Clone> {
    /// The access permission itself.
    permission: P,
    _data: PhantomData<T>,
}

impl<T: permission::Tag, P: Debug + Display + Clone> Display for Access<T, P> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.permission, f)
    }
}

impl<T: permission::Tag, P: Debug + Display + Clone> Deref for Access<T, P> {
    type Target = P;

    fn deref(&self) -> &Self::Target {
        &self.permission
    }
}

/// Various types to identify entities.
pub mod identity;