complete the move

This commit is contained in:
Dietrich 2020-10-06 21:43:12 +02:00
parent 8fa154bdd8
commit 5cff7bf64b
8 changed files with 432 additions and 442 deletions

View File

@ -1,7 +1,7 @@
trait UserDBRead { trait UserDBRead {
fn get_all_users(&self) -> Vec<crate::Passwd>; fn get_all_users(&self) -> Vec<crate::User>;
fn get_user_by_name(&self, name: &str) -> Option<crate::Passwd>; fn get_user_by_name(&self, name: &str) -> Option<crate::User>;
fn get_user_by_id(&self, uid: u64) -> Option<crate::Passwd>; fn get_user_by_id(&self, uid: u64) -> Option<crate::User>;
fn get_all_groups(&self) -> Vec<crate::Group>; fn get_all_groups(&self) -> Vec<crate::Group>;
fn get_group_by_name(&self) -> Option<crate::Group>; fn get_group_by_name(&self) -> Option<crate::Group>;
fn get_group_by_id(&self) -> Option<crate::Group>; fn get_group_by_id(&self) -> Option<crate::Group>;
@ -15,16 +15,16 @@ trait UserDBValidation {
} }
trait UserDBWrite { trait UserDBWrite {
fn set_user(&self) -> Option<crate::Passwd>; fn set_user(&self) -> Option<crate::User>;
fn new_user(&self) -> Option<crate::Passwd>; fn new_user(&self) -> Option<crate::User>;
fn set_group(&self) -> Option<crate::Group>; fn set_group(&self) -> Option<crate::Group>;
fn new_group(&self) -> Option<crate::Group>; fn new_group(&self) -> Option<crate::Group>;
} }
trait UserRead { trait UserRead {
fn get_username(&self) -> Option<crate::Passwd>; fn get_username(&self) -> Option<crate::User>;
fn get_uid(&self) -> Option<crate::Passwd>; fn get_uid(&self) -> Option<crate::User>;
fn get_gid(&self) -> Option<crate::Passwd>; fn get_gid(&self) -> Option<crate::User>;
// … // …
} }

View File

@ -1,7 +1,7 @@
extern crate adduser; extern crate adduser;
use adduser::Passwd;
use adduser::Shadow; use adduser::Shadow;
use adduser::User;
use std::fs::File; use std::fs::File;
use std::io::{prelude::*, BufReader}; use std::io::{prelude::*, BufReader};
@ -18,15 +18,15 @@ fn main() {
for line in reader.lines() { for line in reader.lines() {
let line = line.unwrap(); let line = line.unwrap();
println!("{}", line); println!("{}", line);
println!("{}", Passwd::new_from_string(&line).unwrap()); println!("{}", User::new_from_string(&line).unwrap());
} }
let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"; let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::";
assert_eq!(format!("{}", Shadow::new_from_string(line).unwrap()), line); assert_eq!(format!("{}", Shadow::new_from_string(line).unwrap()), line);
// let pwd = Passwd::default(); // let pwd = User::default();
// let pwd2 = // let pwd2 =
// Passwd::new_from_string("howdy:notencrypted:1001:1001:not done:/home/test:/bin/bash"); // User::new_from_string("howdy:notencrypted:1001:1001:not done:/home/test:/bin/bash");
// println!("Test struct: {}", pwd); // println!("Test struct: {}", pwd);
// assert_eq!(pwd, pwd2.unwrap()) // assert_eq!(pwd, pwd2.unwrap())

View File

@ -9,7 +9,10 @@ pub mod user;
pub mod userlib; pub mod userlib;
pub mod userlib_error; pub mod userlib_error;
pub use group::Group; pub use group::Group;
pub use user::gecos_fields::Gecos;
pub use user::passwd_fields::{ pub use user::passwd_fields::{
EncryptedPassword, Gecos, Gid, HomeDir, Passwd, Password, ShellPath, Uid, Username, EncryptedPassword, Gid, HomeDir, Password, ShellPath, Uid, Username,
}; };
pub use user::shadow_fields::Shadow; pub use user::shadow_fields::Shadow;
pub use user::User;
pub use userlib_error::UserLibError;

223
src/user/gecos_fields.rs Normal file
View File

@ -0,0 +1,223 @@
use crate::UserLibError;
use std::cmp::Eq;
use std::convert::TryFrom;
use std::fmt::{self, Display};
/// The gecos field of a user.
///
/// In the `/etc/passwd` file this field is a `,` sepparated list of items.
/// The first 4 values are more or less standardised to be full name, room, phone at work and phone at home. After that there can be some extra fields often containing the emailadress and even additional information.
///
/// This enum represents the first 4 values by name and adds the other values to a list of strings [`Gecos::Detail`]. If only one field is found and no `,` at all this value is used as a human readable comment [`Gecos::Simple`].
#[derive(Debug, PartialEq, Eq)]
pub enum Gecos<'a> {
Detail {
full_name: &'a str,
room: &'a str,
phone_work: &'a str,
phone_home: &'a str,
other: Option<Vec<&'a str>>,
},
Simple {
comment: &'a str,
},
}
impl<'a> Gecos<'a> {
#[must_use]
pub const fn get_comment(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { comment, .. } => Some(comment),
Gecos::Detail { .. } => None,
}
}
#[must_use]
pub const fn get_full_name(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { full_name, .. } => {
if full_name.is_empty() {
None
} else {
Some(full_name)
}
}
}
}
#[must_use]
pub const fn get_room(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { room, .. } => {
if room.is_empty() {
None
} else {
Some(room)
}
}
}
}
#[must_use]
pub const fn get_phone_work(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_work, .. } => {
if phone_work.is_empty() {
None
} else {
Some(phone_work)
}
}
}
}
#[must_use]
pub const fn get_phone_home(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_home, .. } => {
if phone_home.is_empty() {
None
} else {
Some(phone_home)
}
}
}
}
#[must_use]
pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> {
match self {
Gecos::Simple { .. } => None,
Gecos::Detail { other, .. } => match other {
None => None,
Some(comments) => Some(comments),
},
}
}
}
impl Display for Gecos<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Gecos::Simple { comment } => write!(f, "{}", comment),
Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => write!(
f,
"{},{},{},{}{}",
full_name,
room,
phone_work,
phone_home,
match other {
None => "".to_string(),
Some(cont) => format!(",{}", cont.join(",")),
}
),
}
}
}
impl<'a> TryFrom<&'a str> for Gecos<'a> {
type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
let vals: Vec<&str> = source.split(',').collect();
if vals.len() > 3 {
Ok(Gecos::Detail {
full_name: vals[0],
room: vals[1],
phone_work: vals[2],
phone_home: vals[3],
other: if vals.len() == 4 {
None
} else {
Some(vals[4..].to_vec())
},
})
} else if vals.len() == 1 {
Ok(Gecos::Simple {
comment: vals.get(0).unwrap(),
})
} else {
panic!(format!("Could not parse this string: {}", source))
}
}
}
#[test]
fn test_parse_gecos() {
// test if the Gecos field can be parsed and the resulting struct is populated correctly.
let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com";
let gcsimple = "A böring comment →";
let gc_no_other: &str = "systemd Network Management,,,";
let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap();
match res_simple {
crate::Gecos::Simple { comment } => assert_eq!(comment, "A böring comment →"),
_ => unreachable!(),
}
match res_detail {
crate::Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => {
assert_eq!(full_name, "Full Name");
assert_eq!(room, "504");
assert_eq!(phone_work, "11345342");
assert_eq!(phone_home, "ä1-2312");
assert_eq!(other.unwrap()[0], "myemail@test.com");
}
_ => unreachable!(),
}
match res_no_other {
crate::Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => {
assert_eq!(full_name, "systemd Network Management");
assert_eq!(room, "");
assert_eq!(phone_work, "");
assert_eq!(phone_home, "");
assert_eq!(other, None);
}
_ => unreachable!(),
}
}
#[test]
fn test_gecos_getters() {
// test if the Gecos field can be parsed and the resulting struct is populated correctly.
let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com";
let gcsimple = "A böring comment →";
let gc_no_other: &str = "systemd Network Management,,,";
let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap();
assert_eq!(res_simple.get_comment(), Some("A böring comment →"));
assert_eq!(res_detail.get_comment(), None);
println!("{:?}", res_detail);
assert_eq!(res_detail.get_full_name(), Some("Full Name"));
assert_eq!(res_detail.get_room(), Some("504"));
assert_eq!(res_detail.get_phone_work(), Some("11345342"));
assert_eq!(res_detail.get_phone_home(), Some("ä1-2312"));
assert_eq!(res_detail.get_other(), Some(&vec!["myemail@test.com"]));
assert_eq!(
res_no_other.get_full_name(),
Some("systemd Network Management")
);
assert_eq!(res_no_other.get_room(), None);
assert_eq!(res_no_other.get_phone_work(), None);
assert_eq!(res_no_other.get_phone_home(), None);
assert_eq!(res_no_other.get_other(), None);
}

View File

@ -1,5 +1,10 @@
pub mod gecos_fields;
pub mod passwd_fields; pub mod passwd_fields;
pub mod shadow_fields; pub mod shadow_fields;
use std::convert::TryFrom;
use std::fmt::{self, Display};
/// A record(line) in the user database `/etc/passwd` found in most linux systems. /// A record(line) in the user database `/etc/passwd` found in most linux systems.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct User<'a> { pub struct User<'a> {
@ -12,3 +17,176 @@ pub struct User<'a> {
home_dir: crate::HomeDir<'a>, /* Home directory. */ home_dir: crate::HomeDir<'a>, /* Home directory. */
shell_path: crate::ShellPath<'a>, /* Shell program. */ shell_path: crate::ShellPath<'a>, /* Shell program. */
} }
impl<'a> User<'a> {
/// Parse a line formatted like one in `/etc/passwd` and construct a matching [`adduser::User`] instance
///
/// # Example
/// ```
/// let pwd = adduser::User::new_from_string(
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test"
/// ).unwrap();
/// assert_eq!(pwd.get_username(), "testuser");
/// ```
///
/// # Errors
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
pub fn new_from_string(line: &'a str) -> Result<Self, crate::UserLibError> {
let elements: Vec<&str> = line.split(':').collect();
if elements.len() == 7 {
Ok(Self {
source: line,
username: crate::Username::try_from(*elements.get(0).unwrap())?,
password: crate::Password::Encrypted(crate::EncryptedPassword::try_from(
*elements.get(1).unwrap(),
)?),
uid: crate::Uid::try_from(*elements.get(2).unwrap())?,
gid: crate::Gid::try_from(*elements.get(3).unwrap())?,
gecos: crate::Gecos::try_from(*elements.get(4).unwrap())?,
home_dir: crate::HomeDir::try_from(*elements.get(5).unwrap())?,
shell_path: crate::ShellPath::try_from(*elements.get(6).unwrap())?,
})
} else {
Err("Failed to parse: not enough elements".into())
}
}
#[must_use]
pub const fn get_username(&self) -> &'a str {
self.username.username
}
#[must_use]
pub const fn get_password(&self) -> &'a str {
match self.password {
crate::Password::Encrypted(crate::EncryptedPassword { password }) => password,
crate::Password::Shadow(crate::Shadow { ref password, .. }) => password.password,
crate::Password::Disabled => "x",
}
}
#[must_use]
pub const fn get_uid(&self) -> u32 {
self.uid.uid
}
#[must_use]
pub const fn get_gid(&self) -> u32 {
self.gid.gid
}
#[must_use]
pub const fn get_comment(&self) -> &crate::Gecos {
&self.gecos
}
#[must_use]
pub const fn get_home_dir(&self) -> &'a str {
self.home_dir.dir
}
#[must_use]
pub const fn get_shell_path(&self) -> &'a str {
self.shell_path.shell
}
}
impl Default for User<'_> {
fn default() -> Self {
Self {
source: "",
username: crate::Username {
username: "defaultuser",
},
password: crate::Password::Encrypted(crate::EncryptedPassword {
password: "notencrypted",
}),
uid: crate::Uid { uid: 1001 },
gid: crate::Gid { gid: 1001 },
gecos: crate::Gecos::Simple {
comment: "gecos default comment",
},
home_dir: crate::HomeDir {
dir: "/home/default",
},
shell_path: crate::ShellPath { shell: "/bin/bash" },
}
}
}
impl Display for User<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}:{}:{}:{}:{}:{}",
self.username,
self.password,
self.uid,
self.gid,
self.gecos,
self.home_dir,
self.shell_path
)
}
}
#[test]
fn test_default_user() {
// Check if a user can be created.
let pwd = User::default();
assert_eq!(pwd.username.username, "defaultuser");
assert_eq!(pwd.home_dir.dir, "/home/default");
assert_eq!(pwd.uid.uid, 1001);
}
#[test]
fn test_new_from_string() {
// Test if a single line can be parsed and if the resulting struct is populated correctly.
let fail = User::new_from_string("").err().unwrap();
assert_eq!(
fail,
crate::UserLibError::Message("Failed to parse: not enough elements".into())
);
let pwd =
User::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test")
.unwrap();
let pwd2 =
User::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test")
.unwrap();
assert_eq!(pwd.username.username, "testuser");
assert_eq!(pwd.home_dir.dir, "/home/test");
assert_eq!(pwd.uid.uid, 1001);
match pwd.gecos {
crate::Gecos::Simple { comment } => assert_eq!(comment, "testcomment"),
_ => unreachable!(),
}
match pwd2.gecos {
crate::Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => {
assert_eq!(full_name, "full Name");
assert_eq!(room, "004");
assert_eq!(phone_work, "000342");
assert_eq!(phone_home, "001-2312");
assert_eq!(other.unwrap()[0], "myemail@test.com");
}
_ => unreachable!(),
}
}
#[test]
fn test_parse_passwd() {
// Test wether the passwd file can be parsed and recreated without throwing an exception
use std::fs::File;
use std::io::{prelude::*, BufReader};
let file = File::open("/etc/passwd").unwrap();
let reader = BufReader::new(file);
for line in reader.lines() {
let lineorig: String = line.unwrap();
let linecopy = lineorig.clone();
let pass_struc = User::new_from_string(&linecopy).unwrap();
assert_eq!(
// ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field.
lineorig,
format!("{}", pass_struc)
);
}
}

View File

@ -23,7 +23,7 @@ use std::fmt::{self, Display};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Username<'a> { pub struct Username<'a> {
/// The username value /// The username value
pub(crate) username: &'a str, pub(in crate::user) username: &'a str,
} }
impl Display for Username<'_> { impl Display for Username<'_> {
@ -55,7 +55,7 @@ impl<'a> TryFrom<&'a str> for Username<'a> {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Password<'a> { pub enum Password<'a> {
Encrypted(EncryptedPassword<'a>), Encrypted(crate::EncryptedPassword<'a>),
Shadow(crate::Shadow<'a>), Shadow(crate::Shadow<'a>),
Disabled, Disabled,
} }
@ -72,7 +72,7 @@ impl Display for Password<'_> {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct EncryptedPassword<'a> { pub struct EncryptedPassword<'a> {
pub(crate) password: &'a str, pub(in crate::user) password: &'a str,
} }
impl Display for EncryptedPassword<'_> { impl Display for EncryptedPassword<'_> {
@ -95,7 +95,7 @@ impl<'a> TryFrom<&'a str> for EncryptedPassword<'a> {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Uid { pub struct Uid {
uid: u32, pub(in crate::user) uid: u32,
} }
impl Display for Uid { impl Display for Uid {
@ -123,7 +123,7 @@ impl Uid {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Gid { pub struct Gid {
gid: u32, pub(in crate::user) gid: u32,
} }
impl Display for Gid { impl Display for Gid {
@ -149,153 +149,10 @@ impl Gid {
} }
} }
/// The gecos field of a user.
///
/// In the `/etc/passwd` file this field is a `,` sepparated list of items.
/// The first 4 values are more or less standardised to be full name, room, phone at work and phone at home. After that there can be some extra fields often containing the emailadress and even additional information.
///
/// This enum represents the first 4 values by name and adds the other values to a list of strings [`Gecos::Detail`]. If only one field is found and no `,` at all this value is used as a human readable comment [`Gecos::Simple`].
#[derive(Debug, PartialEq, Eq)]
pub enum Gecos<'a> {
Detail {
full_name: &'a str,
room: &'a str,
phone_work: &'a str,
phone_home: &'a str,
other: Option<Vec<&'a str>>,
},
Simple {
comment: &'a str,
},
}
impl<'a> Gecos<'a> {
#[must_use]
pub const fn get_comment(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { comment, .. } => Some(comment),
Gecos::Detail { .. } => None,
}
}
#[must_use]
pub const fn get_full_name(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { full_name, .. } => {
if full_name.is_empty() {
None
} else {
Some(full_name)
}
}
}
}
#[must_use]
pub const fn get_room(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { room, .. } => {
if room.is_empty() {
None
} else {
Some(room)
}
}
}
}
#[must_use]
pub const fn get_phone_work(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_work, .. } => {
if phone_work.is_empty() {
None
} else {
Some(phone_work)
}
}
}
}
#[must_use]
pub const fn get_phone_home(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_home, .. } => {
if phone_home.is_empty() {
None
} else {
Some(phone_home)
}
}
}
}
#[must_use]
pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> {
match self {
Gecos::Simple { .. } => None,
Gecos::Detail { other, .. } => match other {
None => None,
Some(comments) => Some(comments),
},
}
}
}
impl Display for Gecos<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Gecos::Simple { comment } => write!(f, "{}", comment),
Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => write!(
f,
"{},{},{},{}{}",
full_name,
room,
phone_work,
phone_home,
match other {
None => "".to_string(),
Some(cont) => format!(",{}", cont.join(",")),
}
),
}
}
}
impl<'a> TryFrom<&'a str> for Gecos<'a> {
type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
let vals: Vec<&str> = source.split(',').collect();
if vals.len() > 3 {
Ok(Gecos::Detail {
full_name: vals[0],
room: vals[1],
phone_work: vals[2],
phone_home: vals[3],
other: if vals.len() == 4 {
None
} else {
Some(vals[4..].to_vec())
},
})
} else if vals.len() == 1 {
Ok(Gecos::Simple {
comment: vals.get(0).unwrap(),
})
} else {
panic!(format!("Could not parse this string: {}", source))
}
}
}
/// The home directory of a user /// The home directory of a user
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct HomeDir<'a> { pub struct HomeDir<'a> {
dir: &'a str, pub(in crate::user) dir: &'a str,
} }
impl Display for HomeDir<'_> { impl Display for HomeDir<'_> {
@ -314,7 +171,7 @@ impl<'a> TryFrom<&'a str> for HomeDir<'a> {
/// The path to the Shell binary /// The path to the Shell binary
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ShellPath<'a> { pub struct ShellPath<'a> {
shell: &'a str, pub(in crate::user) shell: &'a str,
} }
impl Display for ShellPath<'_> { impl Display for ShellPath<'_> {
@ -330,124 +187,6 @@ impl<'a> TryFrom<&'a str> for ShellPath<'a> {
} }
} }
/// A record(line) in the user database `/etc/passwd` found in most linux systems.
#[derive(Debug, PartialEq, Eq)]
pub struct Passwd<'a> {
source: &'a str,
username: Username<'a>, /* Username. */
password: Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */
uid: Uid, /* User ID. */
gid: Gid, /* Group ID. */
gecos: Gecos<'a>, /* Real name. */
home_dir: HomeDir<'a>, /* Home directory. */
shell_path: ShellPath<'a>, /* Shell program. */
}
impl<'a> Passwd<'a> {
/// Parse a line formatted like one in `/etc/passwd` and construct a matching `Passwd` instance
///
/// # Example
/// ```
/// let pwd = adduser::passwd::Passwd::new_from_string(
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test"
/// ).unwrap();
/// assert_eq!(pwd.get_username(), "testuser");
/// ```
///
/// # Errors
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
pub fn new_from_string(line: &'a str) -> Result<Self, UserLibError> {
let elements: Vec<&str> = line.split(':').collect();
if elements.len() == 7 {
Ok(Passwd {
source: line,
username: Username::try_from(*elements.get(0).unwrap())?,
password: Password::Encrypted(EncryptedPassword::try_from(
*elements.get(1).unwrap(),
)?),
uid: Uid::try_from(*elements.get(2).unwrap())?,
gid: Gid::try_from(*elements.get(3).unwrap())?,
gecos: Gecos::try_from(*elements.get(4).unwrap())?,
home_dir: HomeDir::try_from(*elements.get(5).unwrap())?,
shell_path: ShellPath::try_from(*elements.get(6).unwrap())?,
})
} else {
Err("Failed to parse: not enough elements".into())
}
}
#[must_use]
pub const fn get_username(&self) -> &'a str {
self.username.username
}
#[must_use]
pub const fn get_password(&self) -> &'a str {
match self.password {
Password::Encrypted(EncryptedPassword { password }) => password,
Password::Shadow(crate::Shadow { ref password, .. }) => password.password,
Password::Disabled => "x",
}
}
#[must_use]
pub const fn get_uid(&self) -> u32 {
self.uid.uid
}
#[must_use]
pub const fn get_gid(&self) -> u32 {
self.gid.gid
}
#[must_use]
pub const fn get_comment(&self) -> &Gecos {
&self.gecos
}
#[must_use]
pub const fn get_home_dir(&self) -> &'a str {
self.home_dir.dir
}
#[must_use]
pub const fn get_shell_path(&self) -> &'a str {
self.shell_path.shell
}
}
impl Default for Passwd<'_> {
fn default() -> Self {
Passwd {
source: "",
username: Username {
username: "defaultuser",
},
password: Password::Encrypted(EncryptedPassword {
password: "notencrypted",
}),
uid: Uid { uid: 1001 },
gid: Gid { gid: 1001 },
gecos: Gecos::Simple {
comment: "gecos default comment",
},
home_dir: HomeDir {
dir: "/home/default",
},
shell_path: ShellPath { shell: "/bin/bash" },
}
}
}
impl Display for Passwd<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}:{}:{}:{}:{}:{}",
self.username,
self.password,
self.uid,
self.gid,
self.gecos,
self.home_dir,
self.shell_path
)
}
}
// Tests ---------------------------------------------------------------------- // Tests ----------------------------------------------------------------------
#[test] #[test]
@ -488,15 +227,6 @@ fn test_username_validation() {
assert_eq!(normal.unwrap().username, "enya$"); assert_eq!(normal.unwrap().username, "enya$");
} }
#[test]
fn test_default_user() {
// Check if a user can be created.
let pwd = Passwd::default();
assert_eq!(pwd.username.username, "defaultuser");
assert_eq!(pwd.home_dir.dir, "/home/default");
assert_eq!(pwd.uid.uid, 1001);
}
#[test] #[test]
fn test_guid_system_user() { fn test_guid_system_user() {
// Check uids of system users. // Check uids of system users.
@ -506,138 +236,3 @@ fn test_guid_system_user() {
assert_eq!(Gid::try_from(val.0).unwrap().is_system_gid(), val.1); assert_eq!(Gid::try_from(val.0).unwrap().is_system_gid(), val.1);
} }
} }
#[test]
fn test_parse_gecos() {
// test if the Gecos field can be parsed and the resulting struct is populated correctly.
let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com";
let gcsimple = "A böring comment →";
let gc_no_other: &str = "systemd Network Management,,,";
let res_detail = Gecos::try_from(gcdetail).unwrap();
let res_simple = Gecos::try_from(gcsimple).unwrap();
let res_no_other = Gecos::try_from(gc_no_other).unwrap();
match res_simple {
Gecos::Simple { comment } => assert_eq!(comment, "A böring comment →"),
_ => unreachable!(),
}
match res_detail {
Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => {
assert_eq!(full_name, "Full Name");
assert_eq!(room, "504");
assert_eq!(phone_work, "11345342");
assert_eq!(phone_home, "ä1-2312");
assert_eq!(other.unwrap()[0], "myemail@test.com");
}
_ => unreachable!(),
}
match res_no_other {
Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => {
assert_eq!(full_name, "systemd Network Management");
assert_eq!(room, "");
assert_eq!(phone_work, "");
assert_eq!(phone_home, "");
assert_eq!(other, None);
}
_ => unreachable!(),
}
}
#[test]
fn test_gecos_getters() {
// test if the Gecos field can be parsed and the resulting struct is populated correctly.
let gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com";
let gcsimple = "A böring comment →";
let gc_no_other: &str = "systemd Network Management,,,";
let res_detail = Gecos::try_from(gcdetail).unwrap();
let res_simple = Gecos::try_from(gcsimple).unwrap();
let res_no_other = Gecos::try_from(gc_no_other).unwrap();
assert_eq!(res_simple.get_comment(), Some("A böring comment →"));
assert_eq!(res_detail.get_comment(), None);
println!("{:?}", res_detail);
assert_eq!(res_detail.get_full_name(), Some("Full Name"));
assert_eq!(res_detail.get_room(), Some("504"));
assert_eq!(res_detail.get_phone_work(), Some("11345342"));
assert_eq!(res_detail.get_phone_home(), Some("ä1-2312"));
assert_eq!(res_detail.get_other(), Some(&vec!["myemail@test.com"]));
assert_eq!(
res_no_other.get_full_name(),
Some("systemd Network Management")
);
assert_eq!(res_no_other.get_room(), None);
assert_eq!(res_no_other.get_phone_work(), None);
assert_eq!(res_no_other.get_phone_home(), None);
assert_eq!(res_no_other.get_other(), None);
}
#[test]
fn test_new_from_string() {
// Test if a single line can be parsed and if the resulting struct is populated correctly.
let fail = Passwd::new_from_string("").err().unwrap();
assert_eq!(
fail,
UserLibError::Message("Failed to parse: not enough elements".into())
);
let pwd =
Passwd::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test")
.unwrap();
let pwd2 =
Passwd::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test")
.unwrap();
assert_eq!(pwd.username.username, "testuser");
assert_eq!(pwd.home_dir.dir, "/home/test");
assert_eq!(pwd.uid.uid, 1001);
match pwd.gecos {
Gecos::Simple { comment } => assert_eq!(comment, "testcomment"),
_ => unreachable!(),
}
match pwd2.gecos {
Gecos::Detail {
full_name,
room,
phone_work,
phone_home,
other,
} => {
assert_eq!(full_name, "full Name");
assert_eq!(room, "004");
assert_eq!(phone_work, "000342");
assert_eq!(phone_home, "001-2312");
assert_eq!(other.unwrap()[0], "myemail@test.com");
}
_ => unreachable!(),
}
}
#[test]
fn test_parse_passwd() {
// Test wether the passwd file can be parsed and recreated without throwing an exception
use std::fs::File;
use std::io::{prelude::*, BufReader};
let file = File::open("/etc/passwd").unwrap();
let reader = BufReader::new(file);
for line in reader.lines() {
let lineorig: String = line.unwrap();
let linecopy = lineorig.clone();
let pass_struc = Passwd::new_from_string(&linecopy).unwrap();
assert_eq!(
// ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field.
lineorig,
format!("{}", pass_struc)
);
}
}

View File

@ -82,7 +82,7 @@ impl<'a> Shadow<'a> {
/// ///
/// # Example /// # Example
/// ``` /// ```
/// let shad = adduser::shadow::Shadow::new_from_string( /// let shad = adduser::Shadow::new_from_string(
/// "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::" /// "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"
/// ).unwrap(); /// ).unwrap();
/// assert_eq!(shad.get_username(), "test"); /// assert_eq!(shad.get_username(), "test");

View File

@ -8,19 +8,11 @@
#![allow(clippy::non_ascii_literal)] #![allow(clippy::non_ascii_literal)]
use log::warn; use log::warn;
use regex::Regex;
use crate::userlib_error::UserLibError;
use std::cmp::Eq;
use std::convert::TryFrom;
use std::fmt::{self, Display};
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
pub struct UserDBLocal<'a> { pub struct UserDBLocal<'a> {
pub(crate) passwd_entries: Vec<crate::Passwd<'a>>, pub passwd_entries: Vec<crate::User<'a>>,
pub(crate) shadow_entries: Vec<crate::Shadow<'a>>, pub shadow_entries: Vec<crate::Shadow<'a>>,
pub(crate) group_entries: Vec<crate::Group<'a>>, pub group_entries: Vec<crate::Group<'a>>,
} }
impl<'a> UserDBLocal<'a> { impl<'a> UserDBLocal<'a> {
@ -36,7 +28,7 @@ impl<'a> UserDBLocal<'a> {
.filter_map(|line| { .filter_map(|line| {
if line.len() > 5 { if line.len() > 5 {
println!("{}", line); println!("{}", line);
Some(crate::Passwd::new_from_string(line).expect("failed to read lines")) Some(crate::User::new_from_string(line).expect("failed to read lines"))
} else { } else {
None None
} }
@ -78,6 +70,8 @@ fn test_creator_user_db_local() {
#[test] #[test]
fn test_parsing_local_database() { fn test_parsing_local_database() {
use std::fs::File;
use std::io::{BufReader, Read};
let passwd_file = File::open("/etc/passwd").unwrap(); let passwd_file = File::open("/etc/passwd").unwrap();
let mut passwd_reader = BufReader::new(passwd_file); let mut passwd_reader = BufReader::new(passwd_file);
let mut my_passwd_lines = "".to_string(); let mut my_passwd_lines = "".to_string();
@ -87,8 +81,5 @@ fn test_parsing_local_database() {
let mut my_group_lines = "".to_string(); let mut my_group_lines = "".to_string();
group_reader.read_to_string(&mut my_group_lines).unwrap(); group_reader.read_to_string(&mut my_group_lines).unwrap();
let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines); let data = UserDBLocal::import_from_strings(&my_passwd_lines, "", &my_group_lines);
assert_eq!( assert_eq!(data.group_entries.get(0).unwrap().get_groupname(), "root");
format!("{}", data.group_entries.get(0).unwrap().get_groupname()),
"teste"
);
} }