add privilege checker
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -343,6 +343,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ license = "SPDX:MIT"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
windows-sys = { version = "0.60.2", features = ["Win32_Security", "Win32_System_SystemServices"]}
|
||||||
winreg = "0.55.0"
|
winreg = "0.55.0"
|
||||||
regex = "1.11.3"
|
regex = "1.11.3"
|
||||||
uuid = "1.18.1"
|
uuid = "1.18.1"
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
use regex::Regex;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
use thiserror::Error as TeError;
|
|
||||||
|
|
||||||
/// The register kind when registering program.
|
|
||||||
pub enum RegisterKind {
|
|
||||||
/// Register for current user.
|
|
||||||
User,
|
|
||||||
/// Register for all users under this computer.
|
|
||||||
System,
|
|
||||||
}
|
|
||||||
|
|
||||||
// region: File Extension
|
|
||||||
|
|
||||||
/// The struct representing an file extension which must start with dot (`.`)
|
|
||||||
/// and followed by at least one arbitrary characters.
|
|
||||||
pub struct FileExt {
|
|
||||||
/// The body of file extension (excluding dot).
|
|
||||||
inner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileExt {
|
|
||||||
pub fn new(file_ext: &str) -> Result<Self, ParseFileExtError> {
|
|
||||||
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap());
|
|
||||||
match RE.captures(file_ext) {
|
|
||||||
Some(v) => Ok(Self {
|
|
||||||
inner: v[1].to_string(),
|
|
||||||
}),
|
|
||||||
None => Err(ParseFileExtError::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when try parsing string into FileExt.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
#[error("given file extension name is illegal")]
|
|
||||||
pub struct ParseFileExtError {}
|
|
||||||
|
|
||||||
impl ParseFileExtError {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FileExt {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, ".{}", self.inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FileExt {
|
|
||||||
type Err = ParseFileExtError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Self::new(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
/// The struct representing an Windows acceptable Prgram ID,
|
|
||||||
/// which looks like `Program.Document.2`
|
|
||||||
pub struct ProgId {
|
|
||||||
inner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgId {
|
|
||||||
pub fn new(prog_id: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: prog_id.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// region: Executable Resource
|
|
||||||
|
|
||||||
/// The struct representing an Windows executable resources path like
|
|
||||||
/// `path_to_file.exe,1`.
|
|
||||||
pub struct ExecRc {
|
|
||||||
/// The path to binary for finding resources.
|
|
||||||
binary: PathBuf,
|
|
||||||
/// The inner index of resources.
|
|
||||||
index: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExecRc {
|
|
||||||
pub fn new(res_str: &str) -> Result<Self, ParseExecRcError> {
|
|
||||||
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap());
|
|
||||||
let caps = RE.captures(res_str);
|
|
||||||
if let Some(caps) = caps {
|
|
||||||
let binary = PathBuf::from_str(&caps[1])?;
|
|
||||||
let index = caps[2].parse::<u32>()?;
|
|
||||||
Ok(Self { binary, index })
|
|
||||||
} else {
|
|
||||||
Err(ParseExecRcError::NoCapture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error occurs when try parsing string into ExecRc.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
#[error("given string is not a valid executable resource string")]
|
|
||||||
pub enum ParseExecRcError {
|
|
||||||
/// Given string is not matched with format.
|
|
||||||
NoCapture,
|
|
||||||
/// Fail to convert executable part into path.
|
|
||||||
BadBinaryPath(#[from] std::convert::Infallible),
|
|
||||||
/// Fail to convert index part into valid number.
|
|
||||||
BadIndex(#[from] std::num::ParseIntError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for ExecRc {
|
|
||||||
type Err = ParseExecRcError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
ExecRc::new(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ExecRc {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{},{}", self.binary.to_str().unwrap(), self.index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
@ -1,15 +0,0 @@
|
|||||||
use thiserror::Error as TeError;
|
|
||||||
|
|
||||||
/// All possible error occurs in this crate.
|
|
||||||
#[derive(Debug, TeError)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("can not register because lack essential privilege. please consider running with Administrator role")]
|
|
||||||
NoPrivilege,
|
|
||||||
#[error("{0}")]
|
|
||||||
BadFileExt(#[from] super::components::ParseFileExtError),
|
|
||||||
#[error("{0}")]
|
|
||||||
BadExecRc(#[from] super::components::ParseExecRcError),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result type used in this crate.
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
@ -1,13 +1,247 @@
|
|||||||
//! This crate provide utilities fetching and manilupating Windows file association.
|
//! This crate provide utilities fetching and manilupating Windows file association.
|
||||||
//! All code under crate are following Microsoft document: https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper
|
//! All code under crate are following Microsoft document: https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
compile_error!("Crate wfassoc is only supported on Windows.");
|
compile_error!("Crate wfassoc is only supported on Windows.");
|
||||||
|
|
||||||
pub(crate) mod error;
|
// region: Error Types
|
||||||
pub(crate) mod components;
|
|
||||||
pub(crate) mod program;
|
|
||||||
|
|
||||||
pub use error::Error as WfError;
|
/// All possible error occurs in this crate.
|
||||||
pub use program::Program;
|
#[derive(Debug, TeError)]
|
||||||
pub use components::RegisterKind;
|
pub enum Error {
|
||||||
|
#[error(
|
||||||
|
"can not register because lack essential privilege. please consider running with Administrator role"
|
||||||
|
)]
|
||||||
|
NoPrivilege,
|
||||||
|
#[error("{0}")]
|
||||||
|
BadFileExt(#[from] ParseFileExtError),
|
||||||
|
#[error("{0}")]
|
||||||
|
BadExecRc(#[from] ParseExecRcError),
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
/// The scope where wfassoc will operate.
|
||||||
|
pub enum Scope {
|
||||||
|
/// Scope for current user.
|
||||||
|
User,
|
||||||
|
/// Scope for all users under this computer.
|
||||||
|
System,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether current process has administrative privilege.
|
||||||
|
///
|
||||||
|
/// It usually means that checking whether current process is running as Administrator.
|
||||||
|
/// Return true if it is, otherwise false.
|
||||||
|
///
|
||||||
|
/// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
|
||||||
|
pub fn has_privilege() -> bool {
|
||||||
|
use windows_sys::core::BOOL;
|
||||||
|
use windows_sys::Win32::Foundation::HANDLE;
|
||||||
|
use windows_sys::Win32::Security::{
|
||||||
|
SECURITY_NT_AUTHORITY, PSID, AllocateAndInitializeSid, CheckTokenMembership, FreeSid
|
||||||
|
};
|
||||||
|
use windows_sys::Win32::System::SystemServices:: {
|
||||||
|
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS
|
||||||
|
};
|
||||||
|
|
||||||
|
let nt_authority = SECURITY_NT_AUTHORITY.clone();
|
||||||
|
let mut administrators_group: PSID = PSID::default();
|
||||||
|
let success: BOOL = unsafe {
|
||||||
|
AllocateAndInitializeSid(
|
||||||
|
&nt_authority,
|
||||||
|
2,
|
||||||
|
SECURITY_BUILTIN_DOMAIN_RID as u32,
|
||||||
|
DOMAIN_ALIAS_RID_ADMINS as u32,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&mut administrators_group,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if success == 0 {
|
||||||
|
panic!("Win32 AllocateAndInitializeSid() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut is_member: BOOL = BOOL::default();
|
||||||
|
let success: BOOL = unsafe {
|
||||||
|
CheckTokenMembership(
|
||||||
|
HANDLE::default(),
|
||||||
|
administrators_group,
|
||||||
|
&mut is_member,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
FreeSid(administrators_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
if success == 0 {
|
||||||
|
panic!("Win32 CheckTokenMembership() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
is_member != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// region: File Extension
|
||||||
|
|
||||||
|
/// The struct representing an file extension which must start with dot (`.`)
|
||||||
|
/// and followed by at least one arbitrary characters.
|
||||||
|
pub struct FileExt {
|
||||||
|
/// The body of file extension (excluding dot).
|
||||||
|
inner: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileExt {
|
||||||
|
pub fn new(file_ext: &str) -> Result<Self, ParseFileExtError> {
|
||||||
|
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap());
|
||||||
|
match RE.captures(file_ext) {
|
||||||
|
Some(v) => Ok(Self {
|
||||||
|
inner: v[1].to_string(),
|
||||||
|
}),
|
||||||
|
None => Err(ParseFileExtError::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error occurs when try parsing string into FileExt.
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
#[error("given file extension name is illegal")]
|
||||||
|
pub struct ParseFileExtError {}
|
||||||
|
|
||||||
|
impl ParseFileExtError {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FileExt {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, ".{}", self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FileExt {
|
||||||
|
type Err = ParseFileExtError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Self::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Executable Resource
|
||||||
|
|
||||||
|
/// The struct representing an Windows executable resources path like
|
||||||
|
/// `path_to_file.exe,1`.
|
||||||
|
pub struct ExecRc {
|
||||||
|
/// The path to binary for finding resources.
|
||||||
|
binary: PathBuf,
|
||||||
|
/// The inner index of resources.
|
||||||
|
index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecRc {
|
||||||
|
pub fn new(res_str: &str) -> Result<Self, ParseExecRcError> {
|
||||||
|
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap());
|
||||||
|
let caps = RE.captures(res_str);
|
||||||
|
if let Some(caps) = caps {
|
||||||
|
let binary = PathBuf::from_str(&caps[1])?;
|
||||||
|
let index = caps[2].parse::<u32>()?;
|
||||||
|
Ok(Self { binary, index })
|
||||||
|
} else {
|
||||||
|
Err(ParseExecRcError::NoCapture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error occurs when try parsing string into ExecRc.
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
#[error("given string is not a valid executable resource string")]
|
||||||
|
pub enum ParseExecRcError {
|
||||||
|
/// Given string is not matched with format.
|
||||||
|
NoCapture,
|
||||||
|
/// Fail to convert executable part into path.
|
||||||
|
BadBinaryPath(#[from] std::convert::Infallible),
|
||||||
|
/// Fail to convert index part into valid number.
|
||||||
|
BadIndex(#[from] std::num::ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ExecRc {
|
||||||
|
type Err = ParseExecRcError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
ExecRc::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ExecRc {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{},{}", self.binary.to_str().unwrap(), self.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// /// The struct representing an Windows acceptable Prgram ID,
|
||||||
|
// /// which looks like `Program.Document.2`
|
||||||
|
// pub struct ProgId {
|
||||||
|
// inner: String,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl ProgId {
|
||||||
|
// pub fn new(prog_id: &str) -> Self {
|
||||||
|
// Self {
|
||||||
|
// inner: prog_id.to_string(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// region: Program
|
||||||
|
|
||||||
|
// /// The struct representing a complete Win32 program.
|
||||||
|
// pub struct Program {
|
||||||
|
// file_exts: Vec<FileExt>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl Program {
|
||||||
|
// /// Create a program descriptor.
|
||||||
|
// pub fn new() -> Self {
|
||||||
|
// Self {
|
||||||
|
// file_exts: Vec::new(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl Program {
|
||||||
|
// /// Register program in this computer
|
||||||
|
// pub fn register(&self, kind: RegisterKind) -> Result<(), Error> {
|
||||||
|
// todo!("pretend to register >_<...")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Unregister program from this computer.
|
||||||
|
// pub fn unregister(&self) -> Result<(), Error> {
|
||||||
|
// todo!("pretend to unregister >_<...")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl Program {
|
||||||
|
// /// Query file extension infos which this program want to associate with.
|
||||||
|
// pub fn query(&self) -> Result<(), Error> {
|
||||||
|
// todo!("pretend to query >_<...")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
@ -1,35 +1,2 @@
|
|||||||
use super::error::{Error, Result};
|
use super::error::{Error, Result};
|
||||||
use super::components::*;
|
use super::components::*;
|
||||||
|
|
||||||
/// The struct representing a complete Win32 program.
|
|
||||||
pub struct Program {
|
|
||||||
file_exts: Vec<FileExt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
/// Create a program descriptor.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
file_exts: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
/// Register program in this computer
|
|
||||||
pub fn register(&self, kind: RegisterKind) -> Result<()> {
|
|
||||||
todo!("pretend to register >_<...")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unregister program from this computer.
|
|
||||||
pub fn unregister(&self) -> Result<()> {
|
|
||||||
todo!("pretend to unregister >_<...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
/// Query file extension infos which this program want to associate with.
|
|
||||||
pub fn query(&self) -> Result<()> {
|
|
||||||
todo!("pretend to query >_<...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,22 @@
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::process;
|
use std::process;
|
||||||
use wfassoc::{Program, RegisterKind, WfError};
|
use wfassoc::{Error as WfError};
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, WfError>;
|
// region: Basic Types
|
||||||
|
|
||||||
|
/// All errors occurs in this executable.
|
||||||
|
#[derive(TeError, Debug)]
|
||||||
|
enum Error {
|
||||||
|
/// Error from wfassoc core.
|
||||||
|
#[error("{0}")]
|
||||||
|
Core(#[from] WfError)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result type used in this executable.
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region: Command Line Parser
|
// region: Command Line Parser
|
||||||
|
|
||||||
@ -35,14 +49,14 @@ enum ForTarget {
|
|||||||
System,
|
System,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ForTarget> for RegisterKind {
|
// impl From<ForTarget> for RegisterKind {
|
||||||
fn from(target: ForTarget) -> Self {
|
// fn from(target: ForTarget) -> Self {
|
||||||
match target {
|
// match target {
|
||||||
ForTarget::User => RegisterKind::User,
|
// ForTarget::User => RegisterKind::User,
|
||||||
ForTarget::System => RegisterKind::System,
|
// ForTarget::System => RegisterKind::System,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
@ -62,19 +76,23 @@ enum Commands {
|
|||||||
// region: Correponding Runner
|
// region: Correponding Runner
|
||||||
|
|
||||||
fn run_register(cli: Cli) -> Result<()> {
|
fn run_register(cli: Cli) -> Result<()> {
|
||||||
let program = Program::new();
|
// let program = Program::new();
|
||||||
let kind: RegisterKind = cli.for_which.into();
|
// let kind: RegisterKind = cli.for_which.into();
|
||||||
program.register(kind)
|
// program.register(kind)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_unregister(cli: Cli) -> Result<()> {
|
fn run_unregister(cli: Cli) -> Result<()> {
|
||||||
let program = Program::new();
|
// let program = Program::new();
|
||||||
program.unregister()
|
// program.unregister()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_query(cli: Cli) -> Result<()> {
|
fn run_query(cli: Cli) -> Result<()> {
|
||||||
let program = Program::new();
|
// let program = Program::new();
|
||||||
program.query()
|
// program.query()?;
|
||||||
|
println!("Has privilege: {}", wfassoc::has_privilege());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
Reference in New Issue
Block a user