Compiles agin.

This commit is contained in:
dietrich 2020-10-07 17:05:49 +02:00
parent a645ebac75
commit 6f59cf17ba
8 changed files with 248 additions and 208 deletions

View File

@ -1,5 +1,6 @@
extern crate adduser; extern crate adduser;
use adduser::NewFromString;
use adduser::Shadow; use adduser::Shadow;
use adduser::User; use adduser::User;
use std::fs::File; use std::fs::File;
@ -17,11 +18,14 @@ fn main() {
for line in reader.lines() { for line in reader.lines() {
let line = line.unwrap(); let line = line.unwrap();
println!("{}", User::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:::".to_string();
assert_eq!(format!("{}", Shadow::new_from_string(line).unwrap()), line); assert_eq!(
format!("{}", Shadow::new_from_string(line.clone()).unwrap()),
line
);
// let pwd = User::default(); // let pwd = User::default();
// let pwd2 = // let pwd2 =

View File

@ -17,24 +17,24 @@ use std::convert::TryFrom;
use std::fmt::{self, Debug, Display}; use std::fmt::{self, Debug, Display};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Groupname<'a> { pub struct Groupname {
groupname: &'a str, groupname: String,
} }
impl Display for Groupname<'_> { impl Display for Groupname {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.groupname,) write!(f, "{}", self.groupname,)
} }
} }
impl<'a> TryFrom<&'a str> for Groupname<'a> { impl TryFrom<String> for Groupname {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
lazy_static! { lazy_static! {
static ref USERVALIDATION: Regex = static ref USERVALIDATION: Regex =
Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap(); Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap();
} }
if USERVALIDATION.is_match(source) { if USERVALIDATION.is_match(&source) {
Ok(Self { groupname: source }) Ok(Self { groupname: source })
} else if source == "Debian-exim" { } else if source == "Debian-exim" {
warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source); warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source);
@ -50,25 +50,25 @@ impl<'a> TryFrom<&'a str> for Groupname<'a> {
/// A record(line) in the user database `/etc/shadow` found in most linux systems. /// A record(line) in the user database `/etc/shadow` found in most linux systems.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Group<'a> { pub struct Group {
groupname: Groupname<'a>, /* Username. */ groupname: Groupname, /* Username. */
pub(crate) password: crate::Password<'a>, /* Usually not used (disabled with x) */ pub(crate) password: crate::Password, /* Usually not used (disabled with x) */
gid: crate::Gid, /* Group ID. */ gid: crate::Gid, /* Group ID. */
members: Vec<crate::Username<'a>>, /* Real name. */ members: Vec<crate::Username>, /* Real name. */
} }
impl<'a> Group<'a> { impl Group {
#[must_use] #[must_use]
pub const fn get_groupname(&self) -> &'a str { pub fn get_groupname(&self) -> &str {
self.groupname.groupname &self.groupname.groupname
} }
#[must_use] #[must_use]
pub const fn get_members(&self) -> &Vec<crate::Username<'a>> { pub const fn get_members(&self) -> &Vec<crate::Username> {
&self.members &self.members
} }
} }
impl<'a> Display for Group<'a> { impl Display for Group {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!( write!(
f, f,
@ -85,7 +85,7 @@ impl<'a> Display for Group<'a> {
} }
} }
impl<'a> NewFromString<'a> for Group<'a> { impl NewFromString for Group {
/// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance
/// ///
/// # Example /// # Example
@ -98,15 +98,15 @@ impl<'a> NewFromString<'a> for Group<'a> {
/// ///
/// # Errors /// # Errors
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
fn new_from_string(line: &'a str) -> Result<Self, UserLibError> { fn new_from_string(line: String) -> Result<Self, UserLibError> {
println!("{}", &line); println!("{}", &line);
let elements: Vec<&str> = line.split(':').collect(); let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
if elements.len() == 4 { if elements.len() == 4 {
Ok(Group { Ok(Group {
groupname: Groupname::try_from(*elements.get(0).unwrap())?, groupname: Groupname::try_from(elements.get(0).unwrap().to_string())?,
password: crate::Password::Disabled, password: crate::Password::Disabled,
gid: crate::Gid::try_from(*elements.get(2).unwrap())?, gid: crate::Gid::try_from(elements.get(2).unwrap().to_string())?,
members: parse_members_list(*elements.get(3).unwrap()), members: parse_members_list(elements.get(3).unwrap().to_string()),
}) })
} else { } else {
Err(UserLibError::Message(format!( Err(UserLibError::Message(format!(
@ -118,9 +118,13 @@ impl<'a> NewFromString<'a> for Group<'a> {
} }
} }
fn parse_members_list<'a>(source: &'a str) -> Vec<crate::Username<'a>> { fn parse_members_list(source: String) -> Vec<crate::Username> {
let mut res = vec![]; let mut res = vec![];
for mem in source.split(',').filter(|x| !x.is_empty()) { for mem in source
.split(',')
.filter(|x| !x.is_empty())
.map(ToString::to_string)
{
res.push(crate::Username::try_from(mem).expect("failed to parse username")); res.push(crate::Username::try_from(mem).expect("failed to parse username"));
} }
res res
@ -129,19 +133,19 @@ fn parse_members_list<'a>(source: &'a str) -> Vec<crate::Username<'a>> {
#[test] #[test]
fn test_parse_and_back_identity() { fn test_parse_and_back_identity() {
let line = "teste:x:1002:test,teste"; let line = "teste:x:1002:test,teste";
let line2 = Group::new_from_string(line).unwrap(); let line2 = Group::new_from_string(line.to_owned()).unwrap();
assert_eq!(format!("{}", line2), line); assert_eq!(format!("{}", line2), line);
} }
#[test] #[test]
fn test_groupname() { fn test_groupname() {
let line = "teste:x:1002:test,teste"; let line = "teste:x:1002:test,teste";
let line2 = Group::new_from_string(line).unwrap(); let line2 = Group::new_from_string(line.to_owned()).unwrap();
assert_eq!(line2.get_groupname(), "teste"); assert_eq!(line2.get_groupname(), "teste");
} }
#[test] #[test]
fn test_root_group() { fn test_root_group() {
let line = "root:x:0:"; let line = "root:x:0:";
let line2 = Group::new_from_string(line).unwrap(); let line2 = Group::new_from_string(line.to_owned()).unwrap();
assert_eq!(line2.get_groupname(), "root"); assert_eq!(line2.get_groupname(), "root");
} }

View File

@ -15,4 +15,5 @@ pub use user::passwd_fields::{
}; };
pub use user::shadow_fields::Shadow; pub use user::shadow_fields::Shadow;
pub use user::User; pub use user::User;
pub use userlib::NewFromString;
pub use userlib_error::UserLibError; pub use userlib_error::UserLibError;

View File

@ -9,81 +9,81 @@ use std::fmt::{self, Display};
/// ///
/// 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`]. /// 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)] #[derive(Debug, PartialEq, Eq)]
pub enum Gecos<'a> { pub enum Gecos {
Detail { Detail {
full_name: &'a str, full_name: String,
room: &'a str, room: String,
phone_work: &'a str, phone_work: String,
phone_home: &'a str, phone_home: String,
other: Option<Vec<&'a str>>, other: Option<Vec<String>>,
}, },
Simple { Simple {
comment: &'a str, comment: String,
}, },
} }
impl<'a> Gecos<'a> { impl<'a> Gecos {
#[must_use] #[must_use]
pub const fn get_comment(&'a self) -> Option<&'a str> { pub fn get_comment(&'a self) -> Option<&'a str> {
match *self { match &self {
Gecos::Simple { comment, .. } => Some(comment), Gecos::Simple { comment, .. } => Some(&comment),
Gecos::Detail { .. } => None, Gecos::Detail { .. } => None,
} }
} }
#[must_use] #[must_use]
pub const fn get_full_name(&'a self) -> Option<&'a str> { pub fn get_full_name(&self) -> Option<&str> {
match *self { match &self {
Gecos::Simple { .. } => None, Gecos::Simple { .. } => None,
Gecos::Detail { full_name, .. } => { Gecos::Detail { full_name, .. } => {
if full_name.is_empty() { if full_name.is_empty() {
None None
} else { } else {
Some(full_name) Some(&full_name)
} }
} }
} }
} }
#[must_use] #[must_use]
pub const fn get_room(&'a self) -> Option<&'a str> { pub fn get_room(&self) -> Option<&str> {
match *self { match &self {
Gecos::Simple { .. } => None, Gecos::Simple { .. } => None,
Gecos::Detail { room, .. } => { Gecos::Detail { room, .. } => {
if room.is_empty() { if room.is_empty() {
None None
} else { } else {
Some(room) Some(&room)
} }
} }
} }
} }
#[must_use] #[must_use]
pub const fn get_phone_work(&'a self) -> Option<&'a str> { pub fn get_phone_work(&self) -> Option<&str> {
match *self { match &self {
Gecos::Simple { .. } => None, Gecos::Simple { .. } => None,
Gecos::Detail { phone_work, .. } => { Gecos::Detail { phone_work, .. } => {
if phone_work.is_empty() { if phone_work.is_empty() {
None None
} else { } else {
Some(phone_work) Some(&phone_work)
} }
} }
} }
} }
#[must_use] #[must_use]
pub const fn get_phone_home(&'a self) -> Option<&'a str> { pub fn get_phone_home(&'_ self) -> Option<&'_ str> {
match *self { match &self {
Gecos::Simple { .. } => None, Gecos::Simple { .. } => None,
Gecos::Detail { phone_home, .. } => { Gecos::Detail { phone_home, .. } => {
if phone_home.is_empty() { if phone_home.is_empty() {
None None
} else { } else {
Some(phone_home) Some(&phone_home)
} }
} }
} }
} }
#[must_use] #[must_use]
pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> { pub const fn get_other(&self) -> Option<&Vec<String>> {
match self { match self {
Gecos::Simple { .. } => None, Gecos::Simple { .. } => None,
Gecos::Detail { other, .. } => match other { Gecos::Detail { other, .. } => match other {
@ -94,7 +94,7 @@ impl<'a> Gecos<'a> {
} }
} }
impl Display for Gecos<'_> { impl Display for Gecos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self { match &self {
Gecos::Simple { comment } => write!(f, "{}", comment), Gecos::Simple { comment } => write!(f, "{}", comment),
@ -120,16 +120,16 @@ impl Display for Gecos<'_> {
} }
} }
impl<'a> TryFrom<&'a str> for Gecos<'a> { impl TryFrom<String> for Gecos {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
let vals: Vec<&str> = source.split(',').collect(); let vals: Vec<String> = source.split(',').map(ToString::to_string).collect();
if vals.len() > 3 { if vals.len() > 3 {
Ok(Gecos::Detail { Ok(Gecos::Detail {
full_name: vals[0], full_name: vals[0].clone(),
room: vals[1], room: vals[1].clone(),
phone_work: vals[2], phone_work: vals[2].clone(),
phone_home: vals[3], phone_home: vals[3].clone(),
other: if vals.len() == 4 { other: if vals.len() == 4 {
None None
} else { } else {
@ -138,7 +138,7 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> {
}) })
} else if vals.len() == 1 { } else if vals.len() == 1 {
Ok(Gecos::Simple { Ok(Gecos::Simple {
comment: vals.get(0).unwrap(), comment: vals.get(0).unwrap().into(),
}) })
} else { } else {
panic!(format!("Could not parse this string: {}", source)) panic!(format!("Could not parse this string: {}", source))
@ -149,9 +149,9 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> {
#[test] #[test]
fn test_parse_gecos() { fn test_parse_gecos() {
// test if the Gecos field can be parsed and the resulting struct is populated correctly. // 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 gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string();
let gcsimple = "A böring comment →"; let gcsimple = "A böring comment →".to_string();
let gc_no_other: &str = "systemd Network Management,,,"; let gc_no_other = "systemd Network Management,,,".to_string();
let res_detail = crate::Gecos::try_from(gcdetail).unwrap(); let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
let res_simple = crate::Gecos::try_from(gcsimple).unwrap(); let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap(); let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap();
@ -196,9 +196,9 @@ fn test_parse_gecos() {
#[test] #[test]
fn test_gecos_getters() { fn test_gecos_getters() {
// test if the Gecos field can be parsed and the resulting struct is populated correctly. // 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 gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string();
let gcsimple = "A böring comment →"; let gcsimple = "A böring comment →".to_string();
let gc_no_other: &str = "systemd Network Management,,,"; let gc_no_other = "systemd Network Management,,,".to_string();
let res_detail = crate::Gecos::try_from(gcdetail).unwrap(); let res_detail = crate::Gecos::try_from(gcdetail).unwrap();
let res_simple = crate::Gecos::try_from(gcsimple).unwrap(); let res_simple = crate::Gecos::try_from(gcsimple).unwrap();
let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap(); let res_no_other = crate::Gecos::try_from(gc_no_other).unwrap();
@ -210,7 +210,10 @@ fn test_gecos_getters() {
assert_eq!(res_detail.get_room(), Some("504")); assert_eq!(res_detail.get_room(), Some("504"));
assert_eq!(res_detail.get_phone_work(), Some("11345342")); assert_eq!(res_detail.get_phone_work(), Some("11345342"));
assert_eq!(res_detail.get_phone_home(), Some("ä1-2312")); assert_eq!(res_detail.get_phone_home(), Some("ä1-2312"));
assert_eq!(res_detail.get_other(), Some(&vec!["myemail@test.com"])); assert_eq!(
res_detail.get_other(),
Some(&vec!["myemail@test.com".to_string()])
);
assert_eq!( assert_eq!(
res_no_other.get_full_name(), res_no_other.get_full_name(),

View File

@ -8,47 +8,47 @@ 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 {
source: &'a str, source: String,
username: crate::Username<'a>, /* Username. */ username: crate::Username, /* Username. */
password: crate::Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */ password: crate::Password, /* Hashed passphrase, if shadow database not in use (see shadow.h). */
uid: crate::Uid, /* User ID. */ uid: crate::Uid, /* User ID. */
gid: crate::Gid, /* Group ID. */ gid: crate::Gid, /* Group ID. */
gecos: crate::Gecos<'a>, /* Real name. */ gecos: crate::Gecos, /* Real name. */
home_dir: crate::HomeDir<'a>, /* Home directory. */ home_dir: crate::HomeDir, /* Home directory. */
shell_path: crate::ShellPath<'a>, /* Shell program. */ shell_path: crate::ShellPath, /* Shell program. */
} }
impl<'a> NewFromString<'a> for User<'a> { impl NewFromString for User {
/// Parse a line formatted like one in `/etc/passwd` and construct a matching [`adduser::User`] instance /// Parse a line formatted like one in `/etc/passwd` and construct a matching [`adduser::User`] instance
/// ///
/// # Example /// # Example
/// ``` /// ```
/// use adduser::NewFromString;
/// let pwd = adduser::User::new_from_string( /// let pwd = adduser::User::new_from_string(
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test" /// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test".to_string()).unwrap();
/// ).unwrap();
/// assert_eq!(pwd.get_username(), "testuser"); /// assert_eq!(pwd.get_username(), "testuser");
/// ``` /// ```
/// ///
/// # Errors /// # Errors
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
fn new_from_string(line: &'a str) -> Result<Self, crate::UserLibError> fn new_from_string(line: String) -> Result<Self, crate::UserLibError>
where where
Self: Sized, Self: Sized,
{ {
let elements: Vec<&str> = line.split(':').collect(); let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
if elements.len() == 7 { if elements.len() == 7 {
Ok(Self { Ok(Self {
source: line, source: line,
username: crate::Username::try_from(*elements.get(0).unwrap())?, username: crate::Username::try_from(elements.get(0).unwrap().to_string())?,
password: crate::Password::Encrypted(crate::EncryptedPassword::try_from( password: crate::Password::Encrypted(crate::EncryptedPassword::try_from(
*elements.get(1).unwrap(), elements.get(1).unwrap().to_string(),
)?), )?),
uid: crate::Uid::try_from(*elements.get(2).unwrap())?, uid: crate::Uid::try_from(elements.get(2).unwrap().to_string())?,
gid: crate::Gid::try_from(*elements.get(3).unwrap())?, gid: crate::Gid::try_from(elements.get(3).unwrap().to_string())?,
gecos: crate::Gecos::try_from(*elements.get(4).unwrap())?, gecos: crate::Gecos::try_from(elements.get(4).unwrap().to_string())?,
home_dir: crate::HomeDir::try_from(*elements.get(5).unwrap())?, home_dir: crate::HomeDir::try_from(elements.get(5).unwrap().to_string())?,
shell_path: crate::ShellPath::try_from(*elements.get(6).unwrap())?, shell_path: crate::ShellPath::try_from(elements.get(6).unwrap().to_string())?,
}) })
} else { } else {
Err("Failed to parse: not enough elements".into()) Err("Failed to parse: not enough elements".into())
@ -56,17 +56,17 @@ impl<'a> NewFromString<'a> for User<'a> {
} }
} }
impl<'a> User<'a> { impl User {
#[must_use] #[must_use]
pub const fn get_username(&self) -> &'a str { pub fn get_username(&self) -> &str {
self.username.username &self.username.username
} }
#[must_use] #[must_use]
pub const fn get_password(&self) -> &'a str { pub fn get_password(&self) -> &str {
match self.password { match &self.password {
crate::Password::Encrypted(crate::EncryptedPassword { password }) => password, crate::Password::Encrypted(crate::EncryptedPassword { password }) => &password,
crate::Password::Shadow(crate::Shadow { ref password, .. }) => password.password, crate::Password::Shadow(crate::Shadow { ref password, .. }) => &password.password,
crate::Password::Disabled => "x", crate::Password::Disabled => &"x",
} }
} }
#[must_use] #[must_use]
@ -82,39 +82,41 @@ impl<'a> User<'a> {
&self.gecos &self.gecos
} }
#[must_use] #[must_use]
pub const fn get_home_dir(&self) -> &'a str { pub fn get_home_dir(&self) -> &str {
self.home_dir.dir &self.home_dir.dir
} }
#[must_use] #[must_use]
pub const fn get_shell_path(&self) -> &'a str { pub fn get_shell_path(&self) -> &str {
self.shell_path.shell &self.shell_path.shell
} }
} }
impl Default for User<'_> { impl Default for User {
fn default() -> Self { fn default() -> Self {
Self { Self {
source: "", source: "".to_owned(),
username: crate::Username { username: crate::Username {
username: "defaultuser", username: "defaultuser".to_owned(),
}, },
password: crate::Password::Encrypted(crate::EncryptedPassword { password: crate::Password::Encrypted(crate::EncryptedPassword {
password: "notencrypted", password: "notencrypted".to_owned(),
}), }),
uid: crate::Uid { uid: 1001 }, uid: crate::Uid { uid: 1001 },
gid: crate::Gid { gid: 1001 }, gid: crate::Gid { gid: 1001 },
gecos: crate::Gecos::Simple { gecos: crate::Gecos::Simple {
comment: "gecos default comment", comment: "gecos default comment".to_string(),
}, },
home_dir: crate::HomeDir { home_dir: crate::HomeDir {
dir: "/home/default", dir: "/home/default".to_owned(),
},
shell_path: crate::ShellPath {
shell: "/bin/bash".to_owned(),
}, },
shell_path: crate::ShellPath { shell: "/bin/bash" },
} }
} }
} }
impl Display for User<'_> { impl Display for User {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
@ -142,16 +144,17 @@ fn test_default_user() {
#[test] #[test]
fn test_new_from_string() { fn test_new_from_string() {
// Test if a single line can be parsed and if the resulting struct is populated correctly. // Test if a single line can be parsed and if the resulting struct is populated correctly.
let fail = User::new_from_string("").err().unwrap(); let fail = User::new_from_string("".into()).err().unwrap();
assert_eq!( assert_eq!(
fail, fail,
crate::UserLibError::Message("Failed to parse: not enough elements".into()) crate::UserLibError::Message("Failed to parse: not enough elements".into())
); );
let pwd = let pwd = User::new_from_string(
User::new_from_string("testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test") "testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test".into(),
.unwrap(); )
.unwrap();
let pwd2 = let pwd2 =
User::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test") User::new_from_string("testuser:testpassword:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test".into())
.unwrap(); .unwrap();
assert_eq!(pwd.username.username, "testuser"); assert_eq!(pwd.username.username, "testuser");
assert_eq!(pwd.home_dir.dir, "/home/test"); assert_eq!(pwd.home_dir.dir, "/home/test");
@ -189,7 +192,7 @@ fn test_parse_passwd() {
for line in reader.lines() { for line in reader.lines() {
let lineorig: String = line.unwrap(); let lineorig: String = line.unwrap();
let linecopy = lineorig.clone(); let linecopy = lineorig.clone();
let pass_struc = User::new_from_string(&linecopy).unwrap(); let pass_struc = User::new_from_string(linecopy).unwrap();
assert_eq!( assert_eq!(
// ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field. // ignoring the numbers of `,` since the implementation does not (yet) reproduce a missing comment field.
lineorig, lineorig,

View File

@ -10,7 +10,7 @@
use log::warn; use log::warn;
use regex::Regex; use regex::Regex;
use crate::userlib_error::UserLibError; use crate::UserLibError;
use std::cmp::Eq; use std::cmp::Eq;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
@ -21,25 +21,25 @@ use std::fmt::{self, Display};
/// ///
/// In the future some extra fields might be added. /// In the future some extra fields might be added.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Username<'a> { pub struct Username {
/// The username value /// The username value
pub(in crate::user) username: &'a str, pub(in crate::user) username: String,
} }
impl Display for Username<'_> { impl Display for Username {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.username,) write!(f, "{}", self.username,)
} }
} }
impl<'a> TryFrom<&'a str> for Username<'a> { impl TryFrom<String> for Username {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
lazy_static! { lazy_static! {
static ref USERVALIDATION: Regex = static ref USERVALIDATION: Regex =
Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap(); Regex::new("^[a-z_]([a-z0-9_\\-]{0,31}|[a-z0-9_\\-]{0,30}\\$)$").unwrap();
} }
if USERVALIDATION.is_match(source) { if USERVALIDATION.is_match(&source) {
Ok(Self { username: source }) Ok(Self { username: source })
} else if source == "Debian-exim" { } else if source == "Debian-exim" {
warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source); warn!("username {} is not a valid username. This might cause problems. (It is default in Debian and Ubuntu)", source);
@ -54,13 +54,13 @@ impl<'a> TryFrom<&'a str> for Username<'a> {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Password<'a> { pub enum Password {
Encrypted(crate::EncryptedPassword<'a>), Encrypted(crate::EncryptedPassword),
Shadow(crate::Shadow<'a>), Shadow(crate::Shadow),
Disabled, Disabled,
} }
impl Display for Password<'_> { impl Display for Password {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,), Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,),
@ -71,19 +71,19 @@ impl Display for Password<'_> {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct EncryptedPassword<'a> { pub struct EncryptedPassword {
pub(in crate::user) password: &'a str, pub(in crate::user) password: String,
} }
impl Display for EncryptedPassword<'_> { impl Display for EncryptedPassword {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.password,) write!(f, "{}", self.password,)
} }
} }
impl<'a> TryFrom<&'a str> for EncryptedPassword<'a> { impl TryFrom<String> for EncryptedPassword {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
if source == "x" { if source == "x" {
warn!("password from shadow not loaded!") warn!("password from shadow not loaded!")
} else { } else {
@ -104,9 +104,9 @@ impl Display for Uid {
} }
} }
impl TryFrom<&str> for Uid { impl TryFrom<String> for Uid {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self { Ok(Self {
uid: source.parse::<u32>().unwrap(), uid: source.parse::<u32>().unwrap(),
}) })
@ -132,9 +132,9 @@ impl Display for Gid {
} }
} }
impl TryFrom<&str> for Gid { impl TryFrom<String> for Gid {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self { Ok(Self {
gid: source.parse::<u32>().unwrap(), gid: source.parse::<u32>().unwrap(),
}) })
@ -151,38 +151,38 @@ impl Gid {
/// 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 {
pub(in crate::user) dir: &'a str, pub(in crate::user) dir: String,
} }
impl Display for HomeDir<'_> { impl Display for HomeDir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.dir,) write!(f, "{}", self.dir,)
} }
} }
impl<'a> TryFrom<&'a str> for HomeDir<'a> { impl TryFrom<String> for HomeDir {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self { dir: source }) Ok(Self { dir: source })
} }
} }
/// 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 {
pub(in crate::user) shell: &'a str, pub(in crate::user) shell: String,
} }
impl Display for ShellPath<'_> { impl Display for ShellPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.shell,) write!(f, "{}", self.shell,)
} }
} }
impl<'a> TryFrom<&'a str> for ShellPath<'a> { impl TryFrom<String> for ShellPath {
type Error = UserLibError; type Error = UserLibError;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> { fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(ShellPath { shell: source }) Ok(ShellPath { shell: source })
} }
} }
@ -192,47 +192,51 @@ impl<'a> TryFrom<&'a str> for ShellPath<'a> {
#[test] #[test]
fn test_username_validation() { fn test_username_validation() {
// Failing tests // Failing tests
let umlauts = Username::try_from("täst"); // umlauts let umlauts: Result<Username, UserLibError> = Username::try_from("täst".to_owned()); // umlauts
assert_eq!( assert_eq!(
Err(UserLibError::Message("Invalid username täst".into())), Err(UserLibError::Message("Invalid username täst".into())),
umlauts umlauts
); );
let number_first = Username::try_from("11elf"); // numbers first let number_first = Username::try_from("11elf".to_owned()); // numbers first
assert_eq!( assert_eq!(
Err(UserLibError::Message("Invalid username 11elf".into())), Err(UserLibError::Message("Invalid username 11elf".into())),
number_first number_first
); );
let slashes = Username::try_from("test/name"); // slashes in the name let slashes = Username::try_from("test/name".to_owned()); // slashes in the name
assert_eq!( assert_eq!(
Err(UserLibError::Message("Invalid username test/name".into())), Err(UserLibError::Message("Invalid username test/name".into())),
slashes slashes
); );
let long = Username::try_from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // maximum size 32 letters let long = Username::try_from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()); // maximum size 32 letters
assert_eq!( assert_eq!(
Err(UserLibError::Message( Err(UserLibError::Message(
"Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into() "Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()
)), )),
long long
); );
// Working tests // Working tests
let ubuntu_exception = Username::try_from("Debian-exim"); // for some reason ubuntu and debian have a capital user. let ubuntu_exception = Username::try_from("Debian-exim".to_owned()); // for some reason ubuntu and debian have a capital user.
assert_eq!(ubuntu_exception.unwrap().username, "Debian-exim"); assert_eq!(ubuntu_exception.unwrap().username, "Debian-exim");
let single = Username::try_from("t"); // single characters are ok let single = Username::try_from("t".to_owned()); // single characters are ok
assert_eq!(single.unwrap().username, "t"); assert_eq!(single.unwrap().username, "t");
let normal = Username::try_from("superman"); // regular username let normal = Username::try_from("superman".to_owned()); // regular username
assert_eq!(normal.unwrap().username, "superman"); assert_eq!(normal.unwrap().username, "superman");
let normal = Username::try_from("anna3pete"); // regular username containing a number let normal = Username::try_from("anna3pete".to_owned()); // regular username containing a number
assert_eq!(normal.unwrap().username, "anna3pete"); assert_eq!(normal.unwrap().username, "anna3pete");
let normal = Username::try_from("enya$"); // regular username ending in a $ let normal = Username::try_from("enya$".to_owned()); // regular username ending in a $
assert_eq!(normal.unwrap().username, "enya$"); assert_eq!(normal.unwrap().username, "enya$");
} }
#[test] #[test]
fn test_guid_system_user() { fn test_guid_system_user() {
// Check uids of system users. // Check uids of system users.
let values = vec![("999", true), ("0", true), ("1000", false)]; let values = vec![
("999".to_owned(), true),
("0".to_owned(), true),
("1000".to_owned(), false),
];
for val in values { for val in values {
assert_eq!(Uid::try_from(val.0).unwrap().is_system_uid(), val.1); assert_eq!(Uid::try_from(val.0.clone()).unwrap().is_system_uid(), val.1);
assert_eq!(Gid::try_from(val.0).unwrap().is_system_gid(), val.1); assert_eq!(Gid::try_from(val.0.clone()).unwrap().is_system_gid(), val.1);
} }
} }

View File

@ -17,30 +17,30 @@ use std::fmt::{self, Debug, Display};
/// A record(line) in the user database `/etc/shadow` found in most linux systems. /// A record(line) in the user database `/etc/shadow` found in most linux systems.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Shadow<'a> { pub struct Shadow {
username: crate::Username<'a>, /* Username. */ username: crate::Username, /* Username. */
pub(crate) password: crate::EncryptedPassword<'a>, /* Hashed passphrase */ pub(crate) password: crate::EncryptedPassword, /* Hashed passphrase */
last_change: Option<chrono::NaiveDateTime>, /* User ID. */ last_change: Option<chrono::NaiveDateTime>, /* User ID. */
earliest_change: Option<chrono::NaiveDateTime>, /* Group ID. */ earliest_change: Option<chrono::NaiveDateTime>, /* Group ID. */
latest_change: Option<chrono::NaiveDateTime>, /* Real name. */ latest_change: Option<chrono::NaiveDateTime>, /* Real name. */
warn_period: Option<chrono::Duration>, /* Home directory. */ warn_period: Option<chrono::Duration>, /* Home directory. */
deactivated: Option<chrono::Duration>, /* Shell program. */ deactivated: Option<chrono::Duration>, /* Shell program. */
deactivated_since: Option<chrono::Duration>, /* Shell program. */ deactivated_since: Option<chrono::Duration>, /* Shell program. */
extensions: Option<u64>, /* Shell program. */ extensions: Option<u64>, /* Shell program. */
} }
impl<'a> Shadow<'a> { impl Shadow {
#[must_use] #[must_use]
pub const fn get_username(&self) -> &'a str { pub fn get_username(&self) -> &str {
self.username.username &self.username.username
} }
#[must_use] #[must_use]
pub const fn get_password(&self) -> &'a str { pub fn get_password(&self) -> &str {
self.password.password &self.password.password
} }
} }
impl<'a> Display for Shadow<'a> { impl Display for Shadow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!( write!(
f, f,
@ -78,27 +78,28 @@ fn show_option_duration(input: Option<chrono::Duration>) -> String {
} }
} }
impl<'a> NewFromString<'a> for Shadow<'a> { impl NewFromString for Shadow {
/// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance /// Parse a line formatted like one in `/etc/shadow` and construct a matching `Shadow` instance
/// ///
/// # Example /// # Example
/// ``` /// ```
/// use adduser::NewFromString;
/// let shad = adduser::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:::".to_string()
/// ).unwrap(); /// ).unwrap();
/// assert_eq!(shad.get_username(), "test"); /// assert_eq!(shad.get_username(), "test");
/// ``` /// ```
/// ///
/// # Errors /// # Errors
/// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed. /// When parsing fails this function returns a `UserLibError::Message` containing some information as to why the function failed.
fn new_from_string(line: &'a str) -> Result<Self, UserLibError> { fn new_from_string(line: String) -> Result<Self, UserLibError> {
println!("{}", &line); println!("{}", &line);
let elements: Vec<&str> = line.split(':').collect(); let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
if elements.len() == 9 { if elements.len() == 9 {
let extra = elements.get(8).unwrap(); let extra = elements.get(8).unwrap();
Ok(Shadow { Ok(Shadow {
username: crate::Username::try_from(*elements.get(0).unwrap())?, username: crate::Username::try_from(elements.get(0).unwrap().to_string())?,
password: crate::EncryptedPassword::try_from(*elements.get(1).unwrap())?, password: crate::EncryptedPassword::try_from(elements.get(1).unwrap().to_string())?,
last_change: date_since_epoch(elements.get(2).unwrap()), last_change: date_since_epoch(elements.get(2).unwrap()),
earliest_change: date_since_epoch(elements.get(3).unwrap()), earliest_change: date_since_epoch(elements.get(3).unwrap()),
latest_change: date_since_epoch(elements.get(4).unwrap()), latest_change: date_since_epoch(elements.get(4).unwrap()),
@ -145,7 +146,7 @@ fn duration_for_days(days_source: &str) -> Option<chrono::Duration> {
fn test_parse_and_back_identity() { fn test_parse_and_back_identity() {
println!("Test"); println!("Test");
let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::"; let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::";
let line2 = Shadow::new_from_string(line).unwrap(); let line2 = Shadow::new_from_string(line.to_owned()).unwrap();
println!("{:#?}", line2); println!("{:#?}", line2);
assert_eq!(format!("{}", line2), line); assert_eq!(format!("{}", line2), line);
} }

View File

@ -12,11 +12,11 @@ use std::fs::File;
use std::io::{BufReader, Read}; use std::io::{BufReader, Read};
use std::path::PathBuf; use std::path::PathBuf;
pub struct UserDBLocal<'a> { pub struct UserDBLocal {
source_files: Files, source_files: Files,
pub passwd_entries: Vec<crate::User<'a>>, pub passwd_entries: Vec<crate::User>,
pub shadow_entries: Vec<crate::Shadow<'a>>, pub shadow_entries: Vec<crate::Shadow>,
pub group_entries: Vec<crate::Group<'a>>, pub group_entries: Vec<crate::Group>,
} }
pub struct Files { pub struct Files {
@ -35,12 +35,12 @@ impl Default for Files {
} }
} }
impl<'a> UserDBLocal<'a> { impl UserDBLocal {
#[must_use] #[must_use]
pub fn import_from_strings( pub fn import_from_strings(
passwd_content: &'a str, passwd_content: String,
shadow_content: &'a str, shadow_content: String,
group_content: &'a str, group_content: String,
) -> Self { ) -> Self {
let res = UserDBLocal { let res = UserDBLocal {
source_files: Files { source_files: Files {
@ -53,7 +53,10 @@ impl<'a> UserDBLocal<'a> {
.filter_map(|line| { .filter_map(|line| {
if line.len() > 5 { if line.len() > 5 {
println!("{}", line); println!("{}", line);
Some(crate::User::new_from_string(line).expect("failed to read lines")) Some(
crate::User::new_from_string(line.to_owned())
.expect("failed to read lines"),
)
} else { } else {
None None
} }
@ -63,7 +66,9 @@ impl<'a> UserDBLocal<'a> {
.lines() .lines()
.filter_map(|line| { .filter_map(|line| {
if line.len() > 5 { if line.len() > 5 {
Some(crate::Group::new_from_string(line).expect("Parsing failed")) Some(
crate::Group::new_from_string(line.to_owned()).expect("Parsing failed"),
)
} else { } else {
None None
} }
@ -73,7 +78,10 @@ impl<'a> UserDBLocal<'a> {
.lines() .lines()
.filter_map(|line| { .filter_map(|line| {
if line.len() > 5 { if line.len() > 5 {
Some(crate::Shadow::new_from_string(line).expect("Parsing failed")) Some(
crate::Shadow::new_from_string(line.to_owned())
.expect("Parsing failed"),
)
} else { } else {
None None
} }
@ -83,19 +91,31 @@ impl<'a> UserDBLocal<'a> {
res res
} }
#[must_use]
pub fn load_files(files: Files) -> Self { pub fn load_files(files: Files) -> Self {
let passwd_file = let passwd_file = File::open(
File::open(files.group.expect("passwd file path cannot be None")).unwrap(); files
.group
.clone()
.expect("passwd file path cannot be None"),
)
.unwrap();
let mut passwd_reader = BufReader::new(passwd_file); let mut passwd_reader = BufReader::new(passwd_file);
let mut my_passwd_lines = String::new(); let mut my_passwd_lines = String::new();
passwd_reader.read_to_string(&mut my_passwd_lines).unwrap(); passwd_reader.read_to_string(&mut my_passwd_lines).unwrap();
let group_file = File::open(files.group.expect("group file path cannot be None")).unwrap(); let group_file =
File::open(files.group.clone().expect("group file path cannot be None")).unwrap();
let mut group_reader = BufReader::new(group_file); let mut group_reader = BufReader::new(group_file);
let mut my_group_lines = String::new(); let mut my_group_lines = String::new();
group_reader.read_to_string(&mut my_group_lines).unwrap(); group_reader.read_to_string(&mut my_group_lines).unwrap();
let shadow_file = File::open(files.shadow.expect("shadow file path cannot be None")) let shadow_file = File::open(
.expect("Failed to read the shadow file. Most of the time root permissions are needed"); files
let mut shadow_reader = BufReader::new(group_file); .shadow
.clone()
.expect("shadow file path cannot be None"),
)
.expect("Failed to read the shadow file. Most of the time root permissions are needed");
let mut shadow_reader = BufReader::new(shadow_file);
let mut my_shadow_lines = String::new(); let mut my_shadow_lines = String::new();
shadow_reader.read_to_string(&mut my_shadow_lines).unwrap(); shadow_reader.read_to_string(&mut my_shadow_lines).unwrap();
@ -108,22 +128,22 @@ impl<'a> UserDBLocal<'a> {
} }
} }
pub trait NewFromString<'a> { pub trait NewFromString {
fn new_from_string(line: &'a str) -> Result<Self, crate::UserLibError> fn new_from_string(line: String) -> Result<Self, crate::UserLibError>
where where
Self: Sized; Self: Sized;
} }
fn string_to<'a, T>(source: &'a str) -> Vec<T> fn string_to<T>(source: &str) -> Vec<T>
where where
T: NewFromString<'a>, T: NewFromString,
{ {
source source
.lines() .lines()
.filter_map(|line| { .filter_map(|line| {
if line.len() > 5 { if line.len() > 5 {
println!("{}", line); println!("{}", line);
Some(T::new_from_string(line).expect("failed to read lines")) Some(T::new_from_string(line.to_owned()).expect("failed to read lines"))
} else { } else {
None None
} }
@ -133,7 +153,7 @@ where
#[test] #[test]
fn test_creator_user_db_local() { fn test_creator_user_db_local() {
let data = UserDBLocal::import_from_strings("testuser:x:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test", "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::", "teste:x:1002:test,teste"); let data = UserDBLocal::import_from_strings("testuser:x:1001:1001:full Name,004,000342,001-2312,myemail@test.com:/home/test:/bin/test".to_string(), "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::".to_string(), "teste:x:1002:test,teste".to_string());
assert_eq!( assert_eq!(
data.passwd_entries.get(0).unwrap().get_username(), data.passwd_entries.get(0).unwrap().get_username(),
"testuser" "testuser"
@ -152,6 +172,6 @@ fn test_parsing_local_database() {
let mut group_reader = BufReader::new(group_file); let mut group_reader = BufReader::new(group_file);
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, "".to_string(), my_group_lines);
assert_eq!(data.group_entries.get(0).unwrap().get_groupname(), "root"); assert_eq!(data.group_entries.get(0).unwrap().get_groupname(), "root");
} }