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;
use adduser::NewFromString;
use adduser::Shadow;
use adduser::User;
use std::fs::File;
@ -17,11 +18,14 @@ fn main() {
for line in reader.lines() {
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:::";
assert_eq!(format!("{}", Shadow::new_from_string(line).unwrap()), line);
let line = "test:!!$6$/RotIe4VZzzAun4W$7YUONvru1rDnllN5TvrnOMsWUD5wSDUPAD6t6/Xwsr/0QOuWF3HcfAhypRkGa8G1B9qqWV5kZSnCb8GKMN9N61:18260:0:99999:7:::".to_string();
assert_eq!(
format!("{}", Shadow::new_from_string(line.clone()).unwrap()),
line
);
// let pwd = User::default();
// let pwd2 =

View File

@ -17,24 +17,24 @@ use std::convert::TryFrom;
use std::fmt::{self, Debug, Display};
#[derive(Debug, PartialEq, Eq)]
pub struct Groupname<'a> {
groupname: &'a str,
pub struct Groupname {
groupname: String,
}
impl Display for Groupname<'_> {
impl Display for Groupname {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.groupname,)
}
}
impl<'a> TryFrom<&'a str> for Groupname<'a> {
impl TryFrom<String> for Groupname {
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! {
static ref USERVALIDATION: Regex =
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 })
} else if source == "Debian-exim" {
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.
#[derive(Debug, PartialEq, Eq)]
pub struct Group<'a> {
groupname: Groupname<'a>, /* Username. */
pub(crate) password: crate::Password<'a>, /* Usually not used (disabled with x) */
gid: crate::Gid, /* Group ID. */
members: Vec<crate::Username<'a>>, /* Real name. */
pub struct Group {
groupname: Groupname, /* Username. */
pub(crate) password: crate::Password, /* Usually not used (disabled with x) */
gid: crate::Gid, /* Group ID. */
members: Vec<crate::Username>, /* Real name. */
}
impl<'a> Group<'a> {
impl Group {
#[must_use]
pub const fn get_groupname(&self) -> &'a str {
self.groupname.groupname
pub fn get_groupname(&self) -> &str {
&self.groupname.groupname
}
#[must_use]
pub const fn get_members(&self) -> &Vec<crate::Username<'a>> {
pub const fn get_members(&self) -> &Vec<crate::Username> {
&self.members
}
}
impl<'a> Display for Group<'a> {
impl Display for Group {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
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
///
/// # Example
@ -98,15 +98,15 @@ impl<'a> NewFromString<'a> for Group<'a> {
///
/// # Errors
/// 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);
let elements: Vec<&str> = line.split(':').collect();
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
if elements.len() == 4 {
Ok(Group {
groupname: Groupname::try_from(*elements.get(0).unwrap())?,
groupname: Groupname::try_from(elements.get(0).unwrap().to_string())?,
password: crate::Password::Disabled,
gid: crate::Gid::try_from(*elements.get(2).unwrap())?,
members: parse_members_list(*elements.get(3).unwrap()),
gid: crate::Gid::try_from(elements.get(2).unwrap().to_string())?,
members: parse_members_list(elements.get(3).unwrap().to_string()),
})
} else {
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![];
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
@ -129,19 +133,19 @@ fn parse_members_list<'a>(source: &'a str) -> Vec<crate::Username<'a>> {
#[test]
fn test_parse_and_back_identity() {
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);
}
#[test]
fn test_groupname() {
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");
}
#[test]
fn test_root_group() {
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");
}

View File

@ -15,4 +15,5 @@ pub use user::passwd_fields::{
};
pub use user::shadow_fields::Shadow;
pub use user::User;
pub use userlib::NewFromString;
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`].
#[derive(Debug, PartialEq, Eq)]
pub enum Gecos<'a> {
pub enum Gecos {
Detail {
full_name: &'a str,
room: &'a str,
phone_work: &'a str,
phone_home: &'a str,
other: Option<Vec<&'a str>>,
full_name: String,
room: String,
phone_work: String,
phone_home: String,
other: Option<Vec<String>>,
},
Simple {
comment: &'a str,
comment: String,
},
}
impl<'a> Gecos<'a> {
impl<'a> Gecos {
#[must_use]
pub const fn get_comment(&'a self) -> Option<&'a str> {
match *self {
Gecos::Simple { comment, .. } => Some(comment),
pub 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 {
pub fn get_full_name(&self) -> Option<&str> {
match &self {
Gecos::Simple { .. } => None,
Gecos::Detail { full_name, .. } => {
if full_name.is_empty() {
None
} else {
Some(full_name)
Some(&full_name)
}
}
}
}
#[must_use]
pub const fn get_room(&'a self) -> Option<&'a str> {
match *self {
pub fn get_room(&self) -> Option<&str> {
match &self {
Gecos::Simple { .. } => None,
Gecos::Detail { room, .. } => {
if room.is_empty() {
None
} else {
Some(room)
Some(&room)
}
}
}
}
#[must_use]
pub const fn get_phone_work(&'a self) -> Option<&'a str> {
match *self {
pub fn get_phone_work(&self) -> Option<&str> {
match &self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_work, .. } => {
if phone_work.is_empty() {
None
} else {
Some(phone_work)
Some(&phone_work)
}
}
}
}
#[must_use]
pub const fn get_phone_home(&'a self) -> Option<&'a str> {
match *self {
pub fn get_phone_home(&'_ self) -> Option<&'_ str> {
match &self {
Gecos::Simple { .. } => None,
Gecos::Detail { phone_home, .. } => {
if phone_home.is_empty() {
None
} else {
Some(phone_home)
Some(&phone_home)
}
}
}
}
#[must_use]
pub const fn get_other(&'a self) -> Option<&Vec<&'a str>> {
pub const fn get_other(&self) -> Option<&Vec<String>> {
match self {
Gecos::Simple { .. } => None,
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 {
match &self {
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;
fn try_from(source: &'a str) -> std::result::Result<Self, Self::Error> {
let vals: Vec<&str> = source.split(',').collect();
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
let vals: Vec<String> = source.split(',').map(ToString::to_string).collect();
if vals.len() > 3 {
Ok(Gecos::Detail {
full_name: vals[0],
room: vals[1],
phone_work: vals[2],
phone_home: vals[3],
full_name: vals[0].clone(),
room: vals[1].clone(),
phone_work: vals[2].clone(),
phone_home: vals[3].clone(),
other: if vals.len() == 4 {
None
} else {
@ -138,7 +138,7 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> {
})
} else if vals.len() == 1 {
Ok(Gecos::Simple {
comment: vals.get(0).unwrap(),
comment: vals.get(0).unwrap().into(),
})
} else {
panic!(format!("Could not parse this string: {}", source))
@ -149,9 +149,9 @@ impl<'a> TryFrom<&'a str> for Gecos<'a> {
#[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 gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string();
let gcsimple = "A böring comment →".to_string();
let gc_no_other = "systemd Network Management,,,".to_string();
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();
@ -196,9 +196,9 @@ fn test_parse_gecos() {
#[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 gcdetail = "Full Name,504,11345342,ä1-2312,myemail@test.com".to_string();
let gcsimple = "A böring comment →".to_string();
let gc_no_other = "systemd Network Management,,,".to_string();
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();
@ -210,7 +210,10 @@ fn test_gecos_getters() {
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_detail.get_other(),
Some(&vec!["myemail@test.com".to_string()])
);
assert_eq!(
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.
#[derive(Debug, PartialEq, Eq)]
pub struct User<'a> {
source: &'a str,
username: crate::Username<'a>, /* Username. */
password: crate::Password<'a>, /* Hashed passphrase, if shadow database not in use (see shadow.h). */
uid: crate::Uid, /* User ID. */
gid: crate::Gid, /* Group ID. */
gecos: crate::Gecos<'a>, /* Real name. */
home_dir: crate::HomeDir<'a>, /* Home directory. */
shell_path: crate::ShellPath<'a>, /* Shell program. */
pub struct User {
source: String,
username: crate::Username, /* Username. */
password: crate::Password, /* Hashed passphrase, if shadow database not in use (see shadow.h). */
uid: crate::Uid, /* User ID. */
gid: crate::Gid, /* Group ID. */
gecos: crate::Gecos, /* Real name. */
home_dir: crate::HomeDir, /* Home directory. */
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
///
/// # Example
/// ```
/// use adduser::NewFromString;
/// let pwd = adduser::User::new_from_string(
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test"
/// ).unwrap();
/// "testuser:testpassword:1001:1001:full Name,,,,:/home/test:/bin/test".to_string()).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.
fn new_from_string(line: &'a str) -> Result<Self, crate::UserLibError>
fn new_from_string(line: String) -> Result<Self, crate::UserLibError>
where
Self: Sized,
{
let elements: Vec<&str> = line.split(':').collect();
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
if elements.len() == 7 {
Ok(Self {
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(
*elements.get(1).unwrap(),
elements.get(1).unwrap().to_string(),
)?),
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())?,
uid: crate::Uid::try_from(elements.get(2).unwrap().to_string())?,
gid: crate::Gid::try_from(elements.get(3).unwrap().to_string())?,
gecos: crate::Gecos::try_from(elements.get(4).unwrap().to_string())?,
home_dir: crate::HomeDir::try_from(elements.get(5).unwrap().to_string())?,
shell_path: crate::ShellPath::try_from(elements.get(6).unwrap().to_string())?,
})
} else {
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]
pub const fn get_username(&self) -> &'a str {
self.username.username
pub fn get_username(&self) -> &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",
pub fn get_password(&self) -> &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]
@ -82,39 +82,41 @@ impl<'a> User<'a> {
&self.gecos
}
#[must_use]
pub const fn get_home_dir(&self) -> &'a str {
self.home_dir.dir
pub fn get_home_dir(&self) -> &str {
&self.home_dir.dir
}
#[must_use]
pub const fn get_shell_path(&self) -> &'a str {
self.shell_path.shell
pub fn get_shell_path(&self) -> &str {
&self.shell_path.shell
}
}
impl Default for User<'_> {
impl Default for User {
fn default() -> Self {
Self {
source: "",
source: "".to_owned(),
username: crate::Username {
username: "defaultuser",
username: "defaultuser".to_owned(),
},
password: crate::Password::Encrypted(crate::EncryptedPassword {
password: "notencrypted",
password: "notencrypted".to_owned(),
}),
uid: crate::Uid { uid: 1001 },
gid: crate::Gid { gid: 1001 },
gecos: crate::Gecos::Simple {
comment: "gecos default comment",
comment: "gecos default comment".to_string(),
},
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 {
write!(
f,
@ -142,16 +144,17 @@ fn test_default_user() {
#[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();
let fail = User::new_from_string("".into()).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 pwd = User::new_from_string(
"testuser:testpassword:1001:1001:testcomment:/home/test:/bin/test".into(),
)
.unwrap();
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();
assert_eq!(pwd.username.username, "testuser");
assert_eq!(pwd.home_dir.dir, "/home/test");
@ -189,7 +192,7 @@ fn test_parse_passwd() {
for line in reader.lines() {
let lineorig: String = line.unwrap();
let linecopy = lineorig.clone();
let pass_struc = User::new_from_string(&linecopy).unwrap();
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,

View File

@ -10,7 +10,7 @@
use log::warn;
use regex::Regex;
use crate::userlib_error::UserLibError;
use crate::UserLibError;
use std::cmp::Eq;
use std::convert::TryFrom;
use std::fmt::{self, Display};
@ -21,25 +21,25 @@ use std::fmt::{self, Display};
///
/// In the future some extra fields might be added.
#[derive(Debug, PartialEq, Eq)]
pub struct Username<'a> {
pub struct Username {
/// 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 {
write!(f, "{}", self.username,)
}
}
impl<'a> TryFrom<&'a str> for Username<'a> {
impl TryFrom<String> for Username {
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! {
static ref USERVALIDATION: Regex =
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 })
} else if source == "Debian-exim" {
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)]
pub enum Password<'a> {
Encrypted(crate::EncryptedPassword<'a>),
Shadow(crate::Shadow<'a>),
pub enum Password {
Encrypted(crate::EncryptedPassword),
Shadow(crate::Shadow),
Disabled,
}
impl Display for Password<'_> {
impl Display for Password {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Password::Encrypted(EncryptedPassword { password }) => write!(f, "{}", password,),
@ -71,19 +71,19 @@ impl Display for Password<'_> {
}
#[derive(Debug, PartialEq, Eq)]
pub struct EncryptedPassword<'a> {
pub(in crate::user) password: &'a str,
pub struct EncryptedPassword {
pub(in crate::user) password: String,
}
impl Display for EncryptedPassword<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl Display for EncryptedPassword {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.password,)
}
}
impl<'a> TryFrom<&'a str> for EncryptedPassword<'a> {
impl TryFrom<String> for EncryptedPassword {
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" {
warn!("password from shadow not loaded!")
} else {
@ -104,9 +104,9 @@ impl Display for Uid {
}
}
impl TryFrom<&str> for Uid {
impl TryFrom<String> for Uid {
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 {
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;
fn try_from(source: &str) -> std::result::Result<Self, Self::Error> {
fn try_from(source: String) -> std::result::Result<Self, Self::Error> {
Ok(Self {
gid: source.parse::<u32>().unwrap(),
})
@ -151,38 +151,38 @@ impl Gid {
/// The home directory of a user
#[derive(Debug, PartialEq, Eq)]
pub struct HomeDir<'a> {
pub(in crate::user) dir: &'a str,
pub struct HomeDir {
pub(in crate::user) dir: String,
}
impl Display for HomeDir<'_> {
impl Display for HomeDir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.dir,)
}
}
impl<'a> TryFrom<&'a str> for HomeDir<'a> {
impl TryFrom<String> for HomeDir {
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 })
}
}
/// The path to the Shell binary
#[derive(Debug, PartialEq, Eq)]
pub struct ShellPath<'a> {
pub(in crate::user) shell: &'a str,
pub struct ShellPath {
pub(in crate::user) shell: String,
}
impl Display for ShellPath<'_> {
impl Display for ShellPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.shell,)
}
}
impl<'a> TryFrom<&'a str> for ShellPath<'a> {
impl TryFrom<String> for ShellPath {
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 })
}
}
@ -192,47 +192,51 @@ impl<'a> TryFrom<&'a str> for ShellPath<'a> {
#[test]
fn test_username_validation() {
// 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!(
Err(UserLibError::Message("Invalid username täst".into())),
umlauts
);
let number_first = Username::try_from("11elf"); // numbers first
let number_first = Username::try_from("11elf".to_owned()); // numbers first
assert_eq!(
Err(UserLibError::Message("Invalid username 11elf".into())),
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!(
Err(UserLibError::Message("Invalid username test/name".into())),
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!(
Err(UserLibError::Message(
"Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()
"Invalid username aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned()
)),
long
);
// 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");
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");
let normal = Username::try_from("superman"); // regular username
let normal = Username::try_from("superman".to_owned()); // regular username
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");
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$");
}
#[test]
fn test_guid_system_user() {
// 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 {
assert_eq!(Uid::try_from(val.0).unwrap().is_system_uid(), val.1);
assert_eq!(Gid::try_from(val.0).unwrap().is_system_gid(), val.1);
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);
}
}

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.
#[derive(Debug, PartialEq, Eq)]
pub struct Shadow<'a> {
username: crate::Username<'a>, /* Username. */
pub(crate) password: crate::EncryptedPassword<'a>, /* Hashed passphrase */
last_change: Option<chrono::NaiveDateTime>, /* User ID. */
earliest_change: Option<chrono::NaiveDateTime>, /* Group ID. */
latest_change: Option<chrono::NaiveDateTime>, /* Real name. */
warn_period: Option<chrono::Duration>, /* Home directory. */
deactivated: Option<chrono::Duration>, /* Shell program. */
deactivated_since: Option<chrono::Duration>, /* Shell program. */
extensions: Option<u64>, /* Shell program. */
pub struct Shadow {
username: crate::Username, /* Username. */
pub(crate) password: crate::EncryptedPassword, /* Hashed passphrase */
last_change: Option<chrono::NaiveDateTime>, /* User ID. */
earliest_change: Option<chrono::NaiveDateTime>, /* Group ID. */
latest_change: Option<chrono::NaiveDateTime>, /* Real name. */
warn_period: Option<chrono::Duration>, /* Home directory. */
deactivated: Option<chrono::Duration>, /* Shell program. */
deactivated_since: Option<chrono::Duration>, /* Shell program. */
extensions: Option<u64>, /* Shell program. */
}
impl<'a> Shadow<'a> {
impl Shadow {
#[must_use]
pub const fn get_username(&self) -> &'a str {
self.username.username
pub fn get_username(&self) -> &str {
&self.username.username
}
#[must_use]
pub const fn get_password(&self) -> &'a str {
self.password.password
pub fn get_password(&self) -> &str {
&self.password.password
}
}
impl<'a> Display for Shadow<'a> {
impl Display for Shadow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
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
///
/// # Example
/// ```
/// use adduser::NewFromString;
/// 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();
/// assert_eq!(shad.get_username(), "test");
/// ```
///
/// # Errors
/// 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);
let elements: Vec<&str> = line.split(':').collect();
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
if elements.len() == 9 {
let extra = elements.get(8).unwrap();
Ok(Shadow {
username: crate::Username::try_from(*elements.get(0).unwrap())?,
password: crate::EncryptedPassword::try_from(*elements.get(1).unwrap())?,
username: crate::Username::try_from(elements.get(0).unwrap().to_string())?,
password: crate::EncryptedPassword::try_from(elements.get(1).unwrap().to_string())?,
last_change: date_since_epoch(elements.get(2).unwrap()),
earliest_change: date_since_epoch(elements.get(3).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() {
println!("Test");
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);
assert_eq!(format!("{}", line2), line);
}

View File

@ -12,11 +12,11 @@ use std::fs::File;
use std::io::{BufReader, Read};
use std::path::PathBuf;
pub struct UserDBLocal<'a> {
pub struct UserDBLocal {
source_files: Files,
pub passwd_entries: Vec<crate::User<'a>>,
pub shadow_entries: Vec<crate::Shadow<'a>>,
pub group_entries: Vec<crate::Group<'a>>,
pub passwd_entries: Vec<crate::User>,
pub shadow_entries: Vec<crate::Shadow>,
pub group_entries: Vec<crate::Group>,
}
pub struct Files {
@ -35,12 +35,12 @@ impl Default for Files {
}
}
impl<'a> UserDBLocal<'a> {
impl UserDBLocal {
#[must_use]
pub fn import_from_strings(
passwd_content: &'a str,
shadow_content: &'a str,
group_content: &'a str,
passwd_content: String,
shadow_content: String,
group_content: String,
) -> Self {
let res = UserDBLocal {
source_files: Files {
@ -53,7 +53,10 @@ impl<'a> UserDBLocal<'a> {
.filter_map(|line| {
if line.len() > 5 {
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 {
None
}
@ -63,7 +66,9 @@ impl<'a> UserDBLocal<'a> {
.lines()
.filter_map(|line| {
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 {
None
}
@ -73,7 +78,10 @@ impl<'a> UserDBLocal<'a> {
.lines()
.filter_map(|line| {
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 {
None
}
@ -83,19 +91,31 @@ impl<'a> UserDBLocal<'a> {
res
}
#[must_use]
pub fn load_files(files: Files) -> Self {
let passwd_file =
File::open(files.group.expect("passwd file path cannot be None")).unwrap();
let passwd_file = File::open(
files
.group
.clone()
.expect("passwd file path cannot be None"),
)
.unwrap();
let mut passwd_reader = BufReader::new(passwd_file);
let mut my_passwd_lines = String::new();
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 my_group_lines = String::new();
group_reader.read_to_string(&mut my_group_lines).unwrap();
let shadow_file = File::open(files.shadow.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(group_file);
let shadow_file = File::open(
files
.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();
shadow_reader.read_to_string(&mut my_shadow_lines).unwrap();
@ -108,22 +128,22 @@ impl<'a> UserDBLocal<'a> {
}
}
pub trait NewFromString<'a> {
fn new_from_string(line: &'a str) -> Result<Self, crate::UserLibError>
pub trait NewFromString {
fn new_from_string(line: String) -> Result<Self, crate::UserLibError>
where
Self: Sized;
}
fn string_to<'a, T>(source: &'a str) -> Vec<T>
fn string_to<T>(source: &str) -> Vec<T>
where
T: NewFromString<'a>,
T: NewFromString,
{
source
.lines()
.filter_map(|line| {
if line.len() > 5 {
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 {
None
}
@ -133,7 +153,7 @@ where
#[test]
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!(
data.passwd_entries.get(0).unwrap().get_username(),
"testuser"
@ -152,6 +172,6 @@ fn test_parsing_local_database() {
let mut group_reader = BufReader::new(group_file);
let mut my_group_lines = "".to_string();
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");
}