#![allow(clippy::non_ascii_literal)]
use log::warn;
use regex::Regex;
use crate::UserLibError;
use std::cmp::Eq;
use std::convert::TryFrom;
use std::fmt::{self, Display};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Username {
pub(crate) username: String,
}
impl Display for Username {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.username,)
}
}
impl TryFrom<String> for Username {
type Error = UserLibError;
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
if is_username_valid(&source) {
Ok(Self { username: source })
} else if source == "Debian-exim" {
warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source);
Ok(Self { username: source })
} else {
Err(format!("Invalid username {}", source).into())
}
}
}
pub(crate) fn is_username_valid(name: &str) -> bool {
lazy_static! {
static ref USERVALIDATION: Regex =
Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap();
}
USERVALIDATION.is_match(name)
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Password {
Encrypted(crate::EncryptedPassword),
Shadow(crate::Shadow),
Disabled,
}
impl Display for Password {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,),
Self::Shadow(_) | Self::Disabled => write!(f, "x"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct EncryptedPassword {
pub(in crate::user) password: String,
}
impl Display for EncryptedPassword {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.password,)
}
}
impl TryFrom<String> for EncryptedPassword {
type Error = UserLibError;
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self { password: source })
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Uid {
pub(in crate::user) uid: u32,
}
impl Display for Uid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.uid,)
}
}
impl TryFrom<String> for Uid {
type Error = UserLibError;
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self {
uid: source.parse::<u32>().unwrap(),
})
}
}
impl Uid {
#[must_use]
pub const fn is_system_uid(&self) -> bool {
self.uid < 1000
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Gid {
pub(in crate::user) gid: u32,
}
impl Display for Gid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.gid,)
}
}
impl TryFrom<String> for Gid {
type Error = UserLibError;
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self {
gid: source.parse::<u32>().unwrap(),
})
}
}
impl Gid {
#[must_use]
pub const fn is_system_gid(&self) -> bool {
self.gid < 1000
}
#[must_use]
pub const fn get_gid(&self) -> u32 {
self.gid
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct HomeDir {
pub(in crate::user) dir: String,
}
impl Display for HomeDir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.dir,)
}
}
impl TryFrom<String> for HomeDir {
type Error = UserLibError;
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self { dir: source })
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ShellPath {
pub(in crate::user) shell: String,
}
impl Display for ShellPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.shell,)
}
}
impl TryFrom<String> for ShellPath {
type Error = UserLibError;
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self { shell: source })
}
}
#[test]
fn test_username_validation() {
let umlauts: Result<Username, UserLibError> = Username::try_from("täst".to_owned()); assert_eq!(Err("Invalid username täst".into()), umlauts);
let number_first = Username::try_from("11elf".to_owned()); assert_eq!(Err("Invalid username 11elf".into()), number_first);
let slashes = Username::try_from("test/name".to_owned()); assert_eq!(Err("Invalid username test/name".into()), slashes);
let long = Username::try_from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()); assert_eq!(
Err("Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()),
long
);
let ubuntu_exception = Username::try_from("Debian-exim".to_owned()); assert_eq!(ubuntu_exception.unwrap().username, "Debian-exim");
let single = Username::try_from("t".to_owned()); assert_eq!(single.unwrap().username, "t");
let normal = Username::try_from("superman".to_owned()); assert_eq!(normal.unwrap().username, "superman");
let normal = Username::try_from("anna3pete".to_owned()); assert_eq!(normal.unwrap().username, "anna3pete");
let normal = Username::try_from("enya$".to_owned()); assert_eq!(normal.unwrap().username, "enya$");
}
#[test]
fn test_guid_system_user() {
let values = vec![
("999".to_owned(), true),
("0".to_owned(), true),
("1000".to_owned(), false),
];
for val in values {
assert_eq!(Uid::try_from(val.0.clone()).unwrap().is_system_uid(), val.1);
assert_eq!(Gid::try_from(val.0.clone()).unwrap().is_system_gid(), val.1);
}
}