pub trait SessionUser: Send + Sync + std::fmt::Debug {
fn user(&self) -> &str;
fn group(&self) -> &str;
fn is_process_user(&self) -> bool;
fn as_any(&self) -> &dyn std::any::Any;
}
#[cfg(unix)]
#[derive(Debug, Clone)]
pub struct PosixSessionUser {
pub user: String,
pub group: String,
}
#[cfg(unix)]
impl PosixSessionUser {
pub fn new(user: &str, group: Option<&str>) -> Self {
let group = match group {
Some(g) => g.to_string(),
None => {
let egid = nix::unistd::getegid();
nix::unistd::Group::from_gid(egid)
.ok()
.flatten()
.map(|g| g.name)
.unwrap_or_else(|| egid.to_string())
}
};
Self {
user: user.to_string(),
group,
}
}
}
#[cfg(unix)]
impl SessionUser for PosixSessionUser {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn user(&self) -> &str {
&self.user
}
fn group(&self) -> &str {
&self.group
}
fn is_process_user(&self) -> bool {
let euid = nix::unistd::geteuid();
nix::unistd::User::from_uid(euid)
.ok()
.flatten()
.map(|u| u.name == self.user)
.unwrap_or(false)
}
}
#[cfg(windows)]
#[derive(Debug, thiserror::Error)]
pub enum BadCredentialsError {
#[error("The username or password is incorrect.")]
LogonFailure,
#[error("{0}")]
Other(String),
}
#[cfg(windows)]
#[derive(Debug)]
pub struct WindowsSessionUser {
user: String,
password: Option<String>,
logon_token: Option<windows::Win32::Foundation::HANDLE>,
}
#[cfg(windows)]
unsafe impl Send for WindowsSessionUser {}
#[cfg(windows)]
unsafe impl Sync for WindowsSessionUser {}
#[cfg(windows)]
impl WindowsSessionUser {
pub fn for_process_user() -> Result<Self, String> {
let user = crate::win32::get_process_user()
.map_err(|e| format!("Failed to get process user: {e}"))?;
Ok(Self {
user,
password: None,
logon_token: None,
})
}
pub fn with_password(user: &str, password: &str) -> Result<Self, BadCredentialsError> {
if crate::win32::is_session_zero() {
return Err(BadCredentialsError::Other(
"Must supply a logon_token rather than a password. \
Passwords are not supported when running in Windows Session 0."
.into(),
));
}
if let Ok(proc_user) = crate::win32::get_process_user() {
if user.eq_ignore_ascii_case(&proc_user) {
return Err(BadCredentialsError::Other(
"User is the process owner. Do not provide a password.".into(),
));
}
}
Self::validate_credentials(user, password)?;
Ok(Self {
user: user.to_string(),
password: Some(password.to_string()),
logon_token: None,
})
}
pub fn with_logon_token(
user: &str,
token: windows::Win32::Foundation::HANDLE,
) -> Result<Self, String> {
if let Ok(proc_user) = crate::win32::get_process_user() {
if user.eq_ignore_ascii_case(&proc_user) {
return Err("User is the process owner. Do not provide a logon token.".into());
}
}
Ok(Self {
user: user.to_string(),
password: None,
logon_token: Some(token),
})
}
pub fn password(&self) -> Option<&str> {
self.password.as_deref()
}
pub fn logon_token(&self) -> Option<windows::Win32::Foundation::HANDLE> {
self.logon_token
}
fn validate_credentials(user: &str, password: &str) -> Result<(), BadCredentialsError> {
match crate::win32::logon_user(user, password) {
Ok(_token) => Ok(()), Err(e) => {
let code = e.code().0 as u32;
if code == 0x8007052E {
Err(BadCredentialsError::LogonFailure)
} else {
Err(BadCredentialsError::Other(e.to_string()))
}
}
}
}
}
#[cfg(windows)]
impl SessionUser for WindowsSessionUser {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn user(&self) -> &str {
&self.user
}
fn group(&self) -> &str {
""
}
fn is_process_user(&self) -> bool {
crate::win32::get_process_user()
.map(|proc_user| self.user.eq_ignore_ascii_case(&proc_user))
.unwrap_or(false)
}
}
#[cfg(all(test, windows))]
mod tests_windows {
use super::*;
#[test]
fn with_password_process_owner_returns_other_variant() {
let proc_user = match crate::win32::get_process_user() {
Ok(u) => u,
Err(_) => return,
};
let result = WindowsSessionUser::with_password(&proc_user, "irrelevant");
match result {
Err(BadCredentialsError::Other(msg)) => {
assert!(
msg.contains("process owner"),
"expected 'process owner' in message, got: {msg}",
);
}
Err(BadCredentialsError::LogonFailure) => {
panic!(
"process-owner rejection should be Other, not LogonFailure. \
The `Other` variant carries the structural-rejection message \
that callers depend on to distinguish this case from a real \
credential mismatch."
);
}
Ok(_) => panic!("with_password(process_user, ...) must reject"),
}
}
}