summaryrefslogtreecommitdiffstats
path: root/vendor/gix-sec/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-sec/src')
-rw-r--r--vendor/gix-sec/src/identity.rs183
-rw-r--r--vendor/gix-sec/src/lib.rs60
-rw-r--r--vendor/gix-sec/src/permission.rs54
-rw-r--r--vendor/gix-sec/src/trust.rs56
4 files changed, 353 insertions, 0 deletions
diff --git a/vendor/gix-sec/src/identity.rs b/vendor/gix-sec/src/identity.rs
new file mode 100644
index 000000000..7c3df38bd
--- /dev/null
+++ b/vendor/gix-sec/src/identity.rs
@@ -0,0 +1,183 @@
+use std::path::Path;
+
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+/// An account based identity
+pub struct Account {
+ /// The user's name
+ pub username: String,
+ /// The user's password
+ pub password: String,
+}
+
+/// Returns true if the given `path` is owned by the user who is executing the current process.
+///
+/// Note that this method is very specific to avoid having to deal with any operating system types.
+pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> {
+ impl_::is_path_owned_by_current_user(path)
+}
+
+#[cfg(not(windows))]
+mod impl_ {
+ use std::path::Path;
+
+ pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> {
+ fn owner_from_path(path: impl AsRef<Path>) -> std::io::Result<u32> {
+ use std::os::unix::fs::MetadataExt;
+ let meta = std::fs::symlink_metadata(path)?;
+ Ok(meta.uid())
+ }
+
+ fn owner_of_current_process() -> std::io::Result<u32> {
+ // SAFETY: there is no documented possibility for failure
+ #[allow(unsafe_code)]
+ let uid = unsafe { libc::geteuid() };
+ Ok(uid)
+ }
+ use std::str::FromStr;
+
+ let owner_of_path = owner_from_path(path)?;
+ let owner_of_process = owner_of_current_process()?;
+ if owner_of_path == owner_of_process {
+ Ok(true)
+ } else if let Some(sudo_uid) =
+ std::env::var_os("SUDO_UID").and_then(|val| val.to_str().and_then(|val_str| u32::from_str(val_str).ok()))
+ {
+ Ok(owner_of_path == sudo_uid)
+ } else {
+ Ok(false)
+ }
+ }
+}
+
+#[cfg(windows)]
+mod impl_ {
+ use std::path::Path;
+
+ fn err(msg: impl Into<String>) -> std::io::Error {
+ std::io::Error::new(std::io::ErrorKind::Other, msg.into())
+ }
+
+ pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> {
+ use windows::{
+ core::{Error, PCWSTR},
+ Win32::{
+ Foundation::{CloseHandle, BOOL, HANDLE, PSID},
+ Security::{
+ Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT},
+ CheckTokenMembership, EqualSid, GetTokenInformation, IsWellKnownSid, TokenOwner,
+ WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_OWNER,
+ TOKEN_QUERY,
+ },
+ System::{
+ Memory::LocalFree,
+ Threading::{GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken},
+ },
+ },
+ };
+
+ let mut err_msg = None;
+ let mut is_owned = false;
+ let path = path.as_ref();
+
+ if !path.exists() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("{:?} does not exist.", path),
+ ));
+ }
+
+ // Home is not actually owned by the corresponding user
+ // but it can be considered de-facto owned by the user
+ // Ignore errors here and just do the regular checks below
+ if gix_path::realpath(path).ok() == dirs::home_dir() {
+ return Ok(true);
+ }
+
+ #[allow(unsafe_code)]
+ unsafe {
+ let mut folder_owner = PSID::default();
+ let mut pdescriptor = PSECURITY_DESCRIPTOR::default();
+ let result = GetNamedSecurityInfoW(
+ PCWSTR(to_wide_path(path).as_ptr()),
+ SE_FILE_OBJECT,
+ OWNER_SECURITY_INFORMATION,
+ Some(&mut folder_owner),
+ None,
+ None,
+ None,
+ &mut pdescriptor,
+ );
+
+ // Workaround for https://github.com/microsoft/win32metadata/issues/884
+ if result.is_ok() {
+ let mut token = HANDLE::default();
+ // Use the current thread token if possible, otherwise open the process token
+ OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, true, &mut token)
+ .ok()
+ .or_else(|_| OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).ok())?;
+
+ let mut buffer_size = 0;
+ let mut buffer = Vec::<u8>::new();
+ GetTokenInformation(token, TokenOwner, None, 0, &mut buffer_size);
+ if buffer_size != 0 {
+ buffer.resize(buffer_size as usize, 0);
+ if GetTokenInformation(
+ token,
+ TokenOwner,
+ Some(buffer.as_mut_ptr() as *mut std::ffi::c_void),
+ buffer_size,
+ &mut buffer_size,
+ )
+ .as_bool()
+ {
+ let token_owner = buffer.as_ptr() as *const TOKEN_OWNER;
+ let token_owner = (*token_owner).Owner;
+
+ is_owned = EqualSid(folder_owner, token_owner).as_bool();
+
+ // Admin-group owned folders are considered owned by the current user, if they are in the admin group
+ if !is_owned && IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid).as_bool() {
+ let mut is_member = BOOL::default();
+ // TODO: re-use the handle
+ match CheckTokenMembership(HANDLE::default(), token_owner, &mut is_member).ok() {
+ Err(e) => err_msg = Some(format!("Couldn't check if user is an administrator: {}", e)),
+ Ok(()) => is_owned = is_member.as_bool(),
+ }
+ }
+ } else {
+ err_msg = format!(
+ "Couldn't get actual token information for current process with err: {}",
+ Error::from_win32()
+ )
+ .into();
+ }
+ } else {
+ err_msg = format!(
+ "Couldn't get token information size info for current process with err: {}",
+ Error::from_win32()
+ )
+ .into();
+ }
+ CloseHandle(token);
+ } else {
+ err_msg = format!(
+ "Couldn't get security information for path '{}' with err {}",
+ path.display(),
+ Error::from_win32()
+ )
+ .into();
+ }
+ LocalFree(pdescriptor.0 as isize);
+ }
+
+ err_msg.map(|msg| Err(err(msg))).unwrap_or(Ok(is_owned))
+ }
+
+ fn to_wide_path(path: impl AsRef<Path>) -> Vec<u16> {
+ use std::os::windows::ffi::OsStrExt;
+ let mut wide_path: Vec<_> = path.as_ref().as_os_str().encode_wide().collect();
+ wide_path.push(0);
+ wide_path
+ }
+}
diff --git a/vendor/gix-sec/src/lib.rs b/vendor/gix-sec/src/lib.rs
new file mode 100644
index 000000000..2f35d98cf
--- /dev/null
+++ b/vendor/gix-sec/src/lib.rs
@@ -0,0 +1,60 @@
+//! 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::{Display, Formatter};
+
+/// 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;
+
+/// 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,
+}
+
+///
+pub mod permission;
+
+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 {
+ std::fmt::Debug::fmt(self, f)
+ }
+}
+
+/// Various types to identify entities.
+pub mod identity;
diff --git a/vendor/gix-sec/src/permission.rs b/vendor/gix-sec/src/permission.rs
new file mode 100644
index 000000000..5bd5f2c32
--- /dev/null
+++ b/vendor/gix-sec/src/permission.rs
@@ -0,0 +1,54 @@
+use std::fmt::{Debug, Display, Formatter};
+
+use crate::Permission;
+
+/// 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.
+#[derive(Debug)]
+pub struct Error<R: std::fmt::Debug> {
+ /// The resource which cannot be used.
+ pub resource: R,
+}
+
+impl<R> Display for Error<R>
+where
+ R: std::fmt::Debug,
+{
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Not allowed to handle resource {:?}: permission denied",
+ self.resource
+ )
+ }
+}
+
+impl<R> std::error::Error for Error<R> where R: std::fmt::Debug {}
+
+impl Permission {
+ /// Return true if this instance is `Permission::Allow`.
+ pub fn is_allowed(&self) -> bool {
+ matches!(self, Permission::Allow)
+ }
+ /// 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.
+ pub fn check<R: std::fmt::Debug>(&self, resource: R) -> Result<Option<R>, Error<R>> {
+ match self {
+ Permission::Allow => Ok(Some(resource)),
+ Permission::Deny => Ok(None),
+ Permission::Forbid => Err(Error { resource }),
+ }
+ }
+
+ /// Like [`check()`][Self::check()], but degenerates the type to an option to make it more useful in cases where
+ /// `Forbid` shouldn't abort the entire operation.
+ pub fn check_opt<R: std::fmt::Debug>(&self, resource: R) -> Option<R> {
+ match self {
+ Permission::Allow => Some(resource),
+ Permission::Deny | Permission::Forbid => None,
+ }
+ }
+}
diff --git a/vendor/gix-sec/src/trust.rs b/vendor/gix-sec/src/trust.rs
new file mode 100644
index 000000000..274c5b780
--- /dev/null
+++ b/vendor/gix-sec/src/trust.rs
@@ -0,0 +1,56 @@
+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(if crate::identity::is_path_owned_by_current_user(path.as_ref())? {
+ Trust::Full
+ } else {
+ 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,
+ }
+ }
+}