Enum to discriminate primary and reg. memberships

This commit is contained in:
Dietrich 2020-11-24 19:20:48 +01:00
parent 93e958108b
commit e9fbbd91ce
Signed by: dietrich
GPG Key ID: 9F3C20C0F85DF67C
5 changed files with 114 additions and 43 deletions

View File

@ -9,7 +9,7 @@ What is working so far:
* Parsing: * Parsing:
* `/etc/passwd` * `/etc/passwd`
* `/etc/shadow` (root permission needed) * `/etc/shadow` (root permission needed)
* `/etc/group` (not yet really assigned to the users) * `/etc/group`
* Modifying: * Modifying:
* delete a user * delete a user
* [x] passwd * [x] passwd

View File

@ -1,4 +1,5 @@
extern crate umanux; extern crate umanux;
use umanux::api::GroupRead;
use umanux::api::UserDBRead; use umanux::api::UserDBRead;
fn main() { fn main() {
@ -13,5 +14,23 @@ fn main() {
for u in db.get_all_users() { for u in db.get_all_users() {
println!("{}", u); println!("{}", u);
println!(
"Groups: {:?}",
u.get_groups()
.iter()
.map(|group| {
(
format!("{:?}", group.0),
group.1.borrow().get_groupname().unwrap().to_owned(),
)
})
.collect::<Vec<(String, String)>>()
);
}
for group in db.get_all_groups() {
let gp = group.borrow();
println!("{}", gp);
println!("{:?}", gp.get_member_names())
} }
} }

View File

@ -4,16 +4,22 @@ use crate::userlib::NewFromString;
use log::warn; use log::warn;
use crate::UserLibError; use crate::UserLibError;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display}; use std::fmt::{self, Debug, Display};
use std::{cell::RefCell, convert::TryFrom};
use std::{cmp::Eq, rc::Rc}; use std::{cmp::Eq, rc::Rc};
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Membership { pub enum MembershipKind {
Primary, Primary,
Member, Member,
} }
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Membership {
kind: MembershipKind,
username: crate::Username,
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Groupname { pub struct Groupname {
groupname: String, groupname: String,
@ -44,7 +50,7 @@ pub(crate) fn is_groupname_valid(name: &str) -> bool {
crate::user::passwd_fields::is_username_valid(name) crate::user::passwd_fields::is_username_valid(name)
} }
pub type Group = Rc<Inner>; pub type Group = Rc<RefCell<Inner>>;
/// 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 Inner { pub struct Inner {
@ -53,7 +59,7 @@ pub struct Inner {
groupname: Groupname, /* Username. */ groupname: Groupname, /* Username. */
pub(crate) password: crate::Password, /* 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>, /* Real name. */ members: Vec<Membership>, /* Real name. */
} }
impl Inner { impl Inner {
@ -65,6 +71,15 @@ impl Inner {
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join("\n") .join("\n")
} }
pub(super) fn append_user(&mut self, username: &str) {
self.members.push(Membership {
kind: MembershipKind::Primary,
username: crate::Username {
username: username.to_owned(),
},
})
}
} }
use crate::api::GroupRead; use crate::api::GroupRead;
@ -77,7 +92,7 @@ impl GroupRead for Inner {
fn get_member_names(&self) -> Option<Vec<&str>> { fn get_member_names(&self) -> Option<Vec<&str>> {
let mut r: Vec<&str> = Vec::new(); let mut r: Vec<&str> = Vec::new();
for u in &self.members { for u in &self.members {
r.push(&u.username); r.push(&u.username.username);
} }
Some(r) Some(r)
} }
@ -101,14 +116,18 @@ impl Display for Inner {
self.gid, self.gid,
self.members self.members
.iter() .iter()
.map(|mem| format!("{}", mem)) .filter_map(|mem| if mem.kind == MembershipKind::Member {
Some(format!("{}", mem.username))
} else {
None
})
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(",") .join(",")
) )
} }
} }
impl NewFromString for Rc<Inner> { impl NewFromString for Group {
/// Parse a line formatted like one in `/etc/group` and construct a matching [`Group`] instance /// Parse a line formatted like one in `/etc/group` and construct a matching [`Group`] instance
/// ///
/// # Example /// # Example
@ -119,7 +138,7 @@ impl NewFromString for Rc<Inner> {
/// "teste:x:1002:test,teste".to_owned(), /// "teste:x:1002:test,teste".to_owned(),
/// 0, /// 0,
/// ).unwrap(); /// ).unwrap();
/// assert_eq!(grp.get_groupname().unwrap(), "teste"); /// assert_eq!(grp.borrow().get_groupname().unwrap(), "teste");
/// ``` /// ```
/// ///
/// # Errors /// # Errors
@ -127,14 +146,14 @@ impl NewFromString for Rc<Inner> {
fn new_from_string(line: String, position: u32) -> Result<Self, UserLibError> { fn new_from_string(line: String, position: u32) -> Result<Self, UserLibError> {
let elements: Vec<String> = line.split(':').map(ToString::to_string).collect(); let elements: Vec<String> = line.split(':').map(ToString::to_string).collect();
if elements.len() == 4 { if elements.len() == 4 {
Ok(Self::new(Inner { Ok(Self::new(RefCell::new(Inner {
pos: position, pos: position,
source: line, source: line,
groupname: Groupname::try_from(elements.get(0).unwrap().to_string())?, 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().to_string())?, 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()),
})) })))
} else { } else {
Err(format!( Err(format!(
"Failed to parse: not enough elements ({}): {:?}", "Failed to parse: not enough elements ({}): {:?}",
@ -146,7 +165,7 @@ impl NewFromString for Rc<Inner> {
} }
} }
fn parse_members_list(source: &str) -> Vec<crate::Username> { fn parse_members_list(source: &str) -> Vec<Membership> {
let mut res = vec![]; let mut res = vec![];
for mem in source.split(',').filter_map(|x| { for mem in source.split(',').filter_map(|x| {
if x.is_empty() { if x.is_empty() {
@ -155,7 +174,10 @@ fn parse_members_list(source: &str) -> Vec<crate::Username> {
Some(x.to_string()) Some(x.to_string())
} }
}) { }) {
res.push(crate::Username::try_from(mem).expect("failed to parse username")); res.push(Membership {
kind: MembershipKind::Member,
username: crate::Username::try_from(mem).expect("failed to parse username"),
});
} }
res res
} }
@ -164,18 +186,18 @@ fn parse_members_list(source: &str) -> Vec<crate::Username> {
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 = Group::new_from_string(line.to_owned(), 0).unwrap(); let line2: Group = Group::new_from_string(line.to_owned(), 0).unwrap();
assert_eq!(format!("{}", line2), line); assert_eq!(format!("{}", line2.borrow()), 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.to_owned(), 0).unwrap(); let line2 = Group::new_from_string(line.to_owned(), 0).unwrap();
assert_eq!(line2.get_groupname().unwrap(), "teste"); assert_eq!(line2.borrow().get_groupname().unwrap(), "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.to_owned(), 0).unwrap(); let line2 = Group::new_from_string(line.to_owned(), 0).unwrap();
assert_eq!(line2.get_groupname().unwrap(), "root"); assert_eq!(line2.borrow().get_groupname().unwrap(), "root");
} }

View File

@ -21,7 +21,7 @@ pub struct User {
gecos: crate::Gecos, /* Real name. */ gecos: crate::Gecos, /* Real name. */
home_dir: crate::HomeDir, /* Home directory. */ home_dir: crate::HomeDir, /* Home directory. */
shell_path: crate::ShellPath, /* Shell program. */ shell_path: crate::ShellPath, /* Shell program. */
groups: Vec<(crate::group::Membership, crate::Group)>, groups: Vec<(crate::group::MembershipKind, crate::Group)>,
} }
impl User { impl User {
@ -70,7 +70,7 @@ impl User {
pub fn add_group( pub fn add_group(
&mut self, &mut self,
group_type: crate::group::Membership, group_type: crate::group::MembershipKind,
group: crate::Group, group: crate::Group,
) -> &mut Self { ) -> &mut Self {
self.groups.push((group_type, group)); self.groups.push((group_type, group));
@ -78,7 +78,7 @@ impl User {
} }
#[must_use] #[must_use]
pub const fn get_groups(&self) -> &Vec<(crate::group::Membership, crate::Group)> { pub const fn get_groups(&self) -> &Vec<(crate::group::MembershipKind, crate::Group)> {
&self.groups &self.groups
} }
} }

View File

@ -34,9 +34,9 @@ impl UserDBLocal {
) -> Self { ) -> Self {
let shadow_entries: Vec<crate::Shadow> = string_to(shadow_content); let shadow_entries: Vec<crate::Shadow> = string_to(shadow_content);
let mut users = user_vec_to_hashmap(string_to(passwd_content)); let mut users = user_vec_to_hashmap(string_to(passwd_content));
let groups = string_to(group_content); let mut groups = string_to(group_content);
shadow_to_users(&mut users, shadow_entries); shadow_to_users(&mut users, shadow_entries);
groups_to_users(&mut users, &groups); groups_to_users(&mut users, &mut groups);
Self { Self {
source_files: files::Files { source_files: files::Files {
passwd: None, passwd: None,
@ -65,9 +65,9 @@ impl UserDBLocal {
let mut users = user_vec_to_hashmap(string_to(&my_passwd_lines)); let mut users = user_vec_to_hashmap(string_to(&my_passwd_lines));
let passwds: Vec<crate::Shadow> = string_to(&my_shadow_lines); let passwds: Vec<crate::Shadow> = string_to(&my_shadow_lines);
let groups: Vec<crate::Group> = string_to(&my_group_lines); let mut groups: Vec<crate::Group> = string_to(&my_group_lines);
shadow_to_users(&mut users, passwds); shadow_to_users(&mut users, passwds);
groups_to_users(&mut users, &groups); groups_to_users(&mut users, &mut groups);
Ok(Self { Ok(Self {
source_files: files, source_files: files,
users, users,
@ -119,7 +119,7 @@ impl UserDBLocal {
group_file_content: &str, group_file_content: &str,
locked_g: &mut files::LockedFileGuard, locked_g: &mut files::LockedFileGuard,
) -> Result<(), UserLibError> { ) -> Result<(), UserLibError> {
let modified_g = group.remove_in(group_file_content); let modified_g = group.borrow().remove_in(group_file_content);
let replace_result = locked_g.replace_contents(modified_g); let replace_result = locked_g.replace_contents(modified_g);
match replace_result { match replace_result {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -147,7 +147,7 @@ impl UserDBLocal {
fn get_group_pos_by_id(&self, id: u32) -> Option<(&crate::Group, usize)> { fn get_group_pos_by_id(&self, id: u32) -> Option<(&crate::Group, usize)> {
for (i, group) in self.groups.iter().enumerate() { for (i, group) in self.groups.iter().enumerate() {
if group.get_gid()? == id { if group.borrow().get_gid()? == id {
return Some((group, i)); return Some((group, i));
} }
} }
@ -197,6 +197,7 @@ impl UserDBWrite for UserDBLocal {
let group = self.get_group_pos_by_id(user.get_gid()); let group = self.get_group_pos_by_id(user.get_gid());
if let Some((group, id)) = group { if let Some((group, id)) = group {
if group if group
.borrow()
.get_member_names() .get_member_names()
.expect("groups have to have members") .expect("groups have to have members")
.len() .len()
@ -207,7 +208,7 @@ impl UserDBWrite for UserDBLocal {
} else { } else {
warn!( warn!(
"The primary group {} was not empty and is thus not removed.", "The primary group {} was not empty and is thus not removed.",
group.get_groupname().unwrap() group.borrow().get_groupname().unwrap()
); );
} }
} else { } else {
@ -293,7 +294,7 @@ impl UserDBRead for UserDBLocal {
fn get_group_by_name(&self, name: &str) -> Option<&crate::Group> { fn get_group_by_name(&self, name: &str) -> Option<&crate::Group> {
for group in &self.groups { for group in &self.groups {
if group.get_groupname()? == name { if group.borrow().get_groupname()? == name {
return Some(group); return Some(group);
} }
} }
@ -302,7 +303,7 @@ impl UserDBRead for UserDBLocal {
fn get_group_by_id(&self, id: u32) -> Option<&crate::Group> { fn get_group_by_id(&self, id: u32) -> Option<&crate::Group> {
for group in &self.groups { for group in &self.groups {
if group.get_gid()? == id { if group.borrow().get_gid()? == id {
return Some(group); return Some(group);
} }
} }
@ -326,7 +327,9 @@ impl UserDBValidation for UserDBLocal {
fn is_gid_valid_and_free(&self, gid: u32) -> bool { fn is_gid_valid_and_free(&self, gid: u32) -> bool {
warn!("No valid check, only free check"); warn!("No valid check, only free check");
self.groups.iter().all(|x| x.get_gid().unwrap() != gid) self.groups
.iter()
.all(|x| x.borrow().get_gid().unwrap() != gid)
} }
fn is_groupname_valid_and_free(&self, name: &str) -> bool { fn is_groupname_valid_and_free(&self, name: &str) -> bool {
@ -334,7 +337,7 @@ impl UserDBValidation for UserDBLocal {
let free = self let free = self
.groups .groups
.iter() .iter()
.all(|x| x.get_groupname().unwrap() != name); .all(|x| x.borrow().get_groupname().unwrap() != name);
valid && free valid && free
} }
} }
@ -350,28 +353,39 @@ fn file_to_string(file: &File) -> Result<String, crate::UserLibError> {
} }
} }
fn groups_to_users<'a>(users: &'a mut UserList, groups: &'a [crate::Group]) -> &'a mut UserList { fn groups_to_users<'a>(
for group in groups { users: &'a mut UserList,
match group.get_member_names() { groups: &'a mut [crate::Group],
) -> &'a mut UserList {
// Populate the regular groups
for group in groups.iter() {
match group.borrow().get_member_names() {
Some(usernames) => { Some(usernames) => {
for username in usernames { for username in usernames {
if let Some(user) = users.get_mut(username) { if let Some(user) = users.get_mut(username) {
user.add_group(crate::group::Membership::Member, group.clone()); user.add_group(crate::group::MembershipKind::Member, group.clone());
} }
} }
} }
None => continue, None => continue,
} }
} }
// Populate the primary membership
for user in users.values_mut() { for user in users.values_mut() {
let gid = user.get_gid(); let gid = user.get_gid();
let grouplist: Vec<&crate::Group> = groups let grouplist: Vec<&crate::Group> = groups
.iter() .iter()
.filter(|g| g.get_gid().unwrap() == gid) .filter(|g| g.borrow().get_gid().unwrap() == gid)
.collect(); .collect();
if grouplist.len() == 1 { if grouplist.len() == 1 {
let group = *grouplist.first().unwrap(); let group = *grouplist.first().unwrap();
user.add_group(crate::group::Membership::Primary, group.clone()); group.borrow_mut().append_user(
user.get_username()
.expect("Users without username are not supported"),
);
user.add_group(crate::group::MembershipKind::Primary, group.clone());
} else { } else {
error!( error!(
"Somehow the group with gid {} was found {} times", "Somehow the group with gid {} was found {} times",
@ -457,10 +471,10 @@ fn test_creator_user_db_local() {
let (member_group1, group1) = user.get_groups().first().unwrap(); let (member_group1, group1) = user.get_groups().first().unwrap();
let (member_group2, group2) = user.get_groups().get(1).unwrap(); let (member_group2, group2) = user.get_groups().get(1).unwrap();
assert_eq!(*member_group1, crate::group::Membership::Member); assert_eq!(*member_group1, crate::group::MembershipKind::Member);
assert_eq!(group1.get_groupname(), Some("another")); assert_eq!(group1.borrow().get_groupname(), Some("another"));
assert_eq!(*member_group2, crate::group::Membership::Primary); assert_eq!(*member_group2, crate::group::MembershipKind::Primary);
assert_eq!(group2.get_groupname(), Some("teste")); assert_eq!(group2.borrow().get_groupname(), Some("teste"));
} }
} }
@ -473,7 +487,15 @@ fn test_parsing_local_database() {
let my_passwd_lines = file_to_string(&pwdfile).unwrap(); let my_passwd_lines = file_to_string(&pwdfile).unwrap();
let my_group_lines = file_to_string(&grpfile).unwrap(); let my_group_lines = file_to_string(&grpfile).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!(data.groups.get(0).unwrap().get_groupname().unwrap(), "root"); assert_eq!(
data.groups
.get(0)
.unwrap()
.borrow()
.get_groupname()
.unwrap(),
"root"
);
} }
#[test] #[test]
@ -495,11 +517,19 @@ fn test_user_db_read_implementation() {
assert!(data.get_all_groups().len() > 10); assert!(data.get_all_groups().len() > 10);
assert!(data.get_group_by_name("root").is_some()); assert!(data.get_group_by_name("root").is_some());
assert_eq!( assert_eq!(
data.get_group_by_name("root").unwrap().get_gid().unwrap(), data.get_group_by_name("root")
.unwrap()
.borrow()
.get_gid()
.unwrap(),
0 0
); );
assert_eq!( assert_eq!(
data.get_group_by_id(0).unwrap().get_groupname().unwrap(), data.get_group_by_id(0)
.unwrap()
.borrow()
.get_groupname()
.unwrap(),
"root" "root"
); );
assert!(data.get_user_by_name("norealnameforsure").is_none()); assert!(data.get_user_by_name("norealnameforsure").is_none());