pub mod action;
mod layout;
mod org;
pub mod permission;
pub mod staff;
mod user_group;
use action::*;
pub use layout::RoleMap;
pub use org::{StaffGov, StaffOrg};
pub use permission::UserPass;
pub use staff::{ProductionRole, Staff};
pub use user_group::UserGroup;
#[cfg(feature = "gui")]
use mktree::*;
use super::*;
#[cfg(feature = "ldap")]
use mkutil::{
ldap,
ldap3::{LdapConn, SearchEntry},
};
const MAX_STAFF_NAME_LENGTH: u8 = 20;
pub trait ActivePersonnel: DynClone + fmt::Debug + Send + Sync {
fn name(&self) -> &String;
fn name_mut(&mut self, name: &str);
fn as_staff(&self) -> &Staff;
#[cfg(feature = "ldap")]
fn person_entry_by_name(
&self,
conn: &mut LdapConn,
base: &str,
attrs: &[&str],
) -> AnyResult<SearchEntry>;
#[cfg(all(feature = "ldap", feature = "image_processing"))]
fn take_profile_img(&mut self) -> Option<AnyResult<RetainedImage>>;
#[cfg(all(feature = "ldap", feature = "image_processing"))]
fn profile_img_mut(&mut self, img: Option<AnyResult<RetainedImage>>);
fn working_role(&self) -> &ProductionRole;
fn is_authorized_for(&mut self, action: &Action) -> bool;
fn is_likely_self(&self, name: &str) -> bool;
fn role_n_permission_mut(
&mut self,
staff: Staff,
#[cfg(all(feature = "ldap", feature = "image_processing"))] profile_img: Option<
AnyResult<RetainedImage>,
>,
clear_cache: bool,
);
fn clear_cache(&mut self) {}
}
dyn_clone::clone_trait_object!(ActivePersonnel);
pub trait PanProjectPersonnel: DynClone + fmt::Debug + Send + Sync {
fn role_in(&self, project: &Project) -> ProductionRole;
fn is_authorized_for_pp(&self, project: &Project, action: &Action) -> bool;
fn pp_staff_mut(&mut self, project: &Project, staff: &Staff);
}
dyn_clone::clone_trait_object!(PanProjectPersonnel);
pub trait Personnel: ActivePersonnel + PanProjectPersonnel {}
dyn_clone::clone_trait_object!(Personnel);
#[derive(Debug, Clone)]
pub struct LocalUser {
staff: Staff,
pp_staff: HashMap<Project, (Staff, UserPass)>,
pass: UserPass,
perm_cache: HashMap<Action, bool>,
}
impl LocalUser {
pub fn from_env() -> Self {
Self {
staff: Staff::from_env(),
pp_staff: HashMap::new(),
pass: UserPass::empty(),
perm_cache: HashMap::new(),
}
}
}
impl ActivePersonnel for LocalUser {
fn name(&self) -> &String {
self.staff.name_unwrap()
}
fn name_mut(&mut self, name: &str) {
self.staff.name_mut(name);
}
fn as_staff(&self) -> &Staff {
&self.staff
}
#[cfg(feature = "ldap")]
fn person_entry_by_name(
&self,
conn: &mut LdapConn,
base: &str,
attrs: &[&str],
) -> AnyResult<SearchEntry> {
let person_filter = &ClientCfgCel::settings().as_ref()?.ldap_raw().person_filter;
let filter = format!(
"(&(objectClass=person)({}={}*))",
person_filter,
self.name()
);
info!("Using LDAP filter: {}", filter);
Ok(
ldap::search_subtree_for_entries(conn, base, &filter, attrs)?
.into_iter()
.next()
.context(format!("No entry found that matches filter \"{}\"", filter))?,
)
}
#[cfg(all(feature = "ldap", feature = "image_processing"))]
fn take_profile_img(&mut self) -> Option<AnyResult<RetainedImage>> {
self.staff.profile_img.take()
}
#[cfg(all(feature = "ldap", feature = "image_processing"))]
fn profile_img_mut(&mut self, img: Option<AnyResult<RetainedImage>>) {
match &img {
Some(Ok(_)) => {
info!(
"{} {}",
"Setting profile image for".on_bright_magenta(),
self.name()
);
}
Some(Err(e)) => {
warn!("{}: {}", "Profile image error".on_bright_red(), e);
}
None => {
warn!("{}", "Profile image is none".on_bright_blue());
}
}
self.staff.profile_img = img;
}
fn working_role(&self) -> &ProductionRole {
&self.staff.role
}
fn is_authorized_for(&mut self, action: &Action) -> bool {
match self.perm_cache.get(action) {
Some(authorized) => *authorized,
None => {
let authorized = self.pass.is_authorized_for(action);
self.perm_cache.insert(action.clone(), authorized);
authorized
}
}
}
fn is_likely_self(&self, name: &str) -> bool {
name == self.name()
}
fn role_n_permission_mut(
&mut self,
staff: Staff,
#[cfg(all(feature = "ldap", feature = "image_processing"))] profile_img: Option<
AnyResult<RetainedImage>,
>,
clear_cache: bool,
) {
debug!("Updated Staff: {:?}", staff);
if clear_cache {
self.clear_cache();
};
self.pass = staff.role.user_pass();
self.staff = staff;
#[cfg(all(feature = "ldap", feature = "image_processing"))]
self.profile_img_mut(profile_img);
}
fn clear_cache(&mut self) {
debug!("Clearing permission cache");
self.perm_cache = HashMap::new();
}
}
impl PanProjectPersonnel for LocalUser {
fn role_in(&self, project: &Project) -> ProductionRole {
match self.pp_staff.get(project) {
Some((staff, _)) => staff.role.clone(),
None => ProductionRole::Undefined,
}
}
fn is_authorized_for_pp(&self, project: &Project, action: &Action) -> bool {
match self.pp_staff.get(project) {
Some((_, user_pass)) => user_pass.is_authorized_for(action),
None => false,
}
}
fn pp_staff_mut(&mut self, project: &Project, staff: &Staff) {
if !self.pp_staff.contains_key(project) {
self.pp_staff
.insert(project.clone(), (staff.clone(), staff.role.user_pass()));
};
}
}
impl Personnel for LocalUser {}
pub fn make_assignee_str<'a>(staves: impl Iterator<Item = &'a Staff>) -> String {
let staves = staves
.into_iter()
.map(|a| a.name_unwrap().to_owned())
.collect::<Vec<String>>();
if staves.is_empty() {
"!? UNASSIGNED 🎁".to_string()
} else {
format!("Assignees: {}", staves.join(", "))
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use hconf::ClientCfgCel;
#[test]
#[cfg(feature = "ldap")]
fn connect_ldap() {
use hconf::ReadFromFiles;
use mkutil::ldap;
ClientCfgCel::pkg_test(true);
let cfg = ClientCfgCel::settings().as_ref().unwrap();
let auth = cfg.ldap_auth().unwrap();
let (mut conn, mut _bind) =
ldap::authenticate(&auth.url, &auth.distinguished_name, &auth.password).unwrap();
let search = ldap::search_subtree_for_entries(
&mut conn,
&auth.search_base,
"(objectClass=person)",
&["givenName", "sn", "mail", "cn", "uid"],
);
assert!(search.is_ok());
for e in &search {
eprintln!("Search result: {:?}", e);
}
}
}