add privilege checker
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -343,6 +343,7 @@ dependencies = [
|
||||
"regex",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"windows-sys 0.60.2",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
|
@ -8,6 +8,7 @@ license = "SPDX:MIT"
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
windows-sys = { version = "0.60.2", features = ["Win32_Security", "Win32_System_SystemServices"]}
|
||||
winreg = "0.55.0"
|
||||
regex = "1.11.3"
|
||||
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.
|
||||
//! 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"))]
|
||||
compile_error!("Crate wfassoc is only supported on Windows.");
|
||||
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod components;
|
||||
pub(crate) mod program;
|
||||
// region: Error Types
|
||||
|
||||
pub use error::Error as WfError;
|
||||
pub use program::Program;
|
||||
pub use components::RegisterKind;
|
||||
/// 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] 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::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 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
|
||||
|
||||
@ -35,14 +49,14 @@ enum ForTarget {
|
||||
System,
|
||||
}
|
||||
|
||||
impl From<ForTarget> for RegisterKind {
|
||||
fn from(target: ForTarget) -> Self {
|
||||
match target {
|
||||
ForTarget::User => RegisterKind::User,
|
||||
ForTarget::System => RegisterKind::System,
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl From<ForTarget> for RegisterKind {
|
||||
// fn from(target: ForTarget) -> Self {
|
||||
// match target {
|
||||
// ForTarget::User => RegisterKind::User,
|
||||
// ForTarget::System => RegisterKind::System,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
@ -62,19 +76,23 @@ enum Commands {
|
||||
// region: Correponding Runner
|
||||
|
||||
fn run_register(cli: Cli) -> Result<()> {
|
||||
let program = Program::new();
|
||||
let kind: RegisterKind = cli.for_which.into();
|
||||
program.register(kind)
|
||||
// let program = Program::new();
|
||||
// let kind: RegisterKind = cli.for_which.into();
|
||||
// program.register(kind)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_unregister(cli: Cli) -> Result<()> {
|
||||
let program = Program::new();
|
||||
program.unregister()
|
||||
// let program = Program::new();
|
||||
// program.unregister()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_query(cli: Cli) -> Result<()> {
|
||||
let program = Program::new();
|
||||
program.query()
|
||||
// let program = Program::new();
|
||||
// program.query()?;
|
||||
println!("Has privilege: {}", wfassoc::has_privilege());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
Reference in New Issue
Block a user