1
0

add privilege checker

This commit is contained in:
2025-10-09 13:53:31 +08:00
parent 03a1796e07
commit 682be6ab58
7 changed files with 277 additions and 200 deletions

1
Cargo.lock generated
View File

@ -343,6 +343,7 @@ dependencies = [
"regex", "regex",
"thiserror", "thiserror",
"uuid", "uuid",
"windows-sys 0.60.2",
"winreg", "winreg",
] ]

View File

@ -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"

View File

@ -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

View File

@ -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>;

View File

@ -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

View File

@ -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 >_<...")
}
}

View File

@ -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