diff --git a/bffhd/authorization/roles.rs b/bffhd/authorization/roles.rs index d722c00..436a81e 100644 --- a/bffhd/authorization/roles.rs +++ b/bffhd/authorization/roles.rs @@ -1,7 +1,8 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt; use once_cell::sync::OnceCell; -use crate::authorization::permissions::PermRule; +use crate::authorization::permissions::{Permission, PermRule}; +use crate::users::db::UserData; static ROLES: OnceCell> = OnceCell::new(); @@ -25,6 +26,78 @@ impl Roles { pub fn get(self, roleid: &str) -> Option<&Role> { self.roles.get(roleid) } + + + /// Tally a role dependency tree into a set + /// + /// A Default implementation exists which adapter may overwrite with more efficient + /// implementations. + fn tally_role(&self, roles: &mut HashMap, role_id: &String) { + if let Some(role) = self.get(role_id) { + // Only check and tally parents of a role at the role itself if it's the first time we + // see it + if !roles.contains_key(role_id) { + for parent in role.parents.iter() { + self.tally_role(roles, parent); + } + + roles.insert(role_id.clone(), role.clone()); + } + } + } + + fn collect_permrules(&self, user: &UserData) -> Vec { + let mut roleset = HashMap::new(); + for role_id in user.roles.iter() { + self.tally_role(&mut roleset, role_id); + } + + let mut output = Vec::new(); + + // Iter all unique role->permissions we've found and early return on match. + for (_roleid, role) in roleset.iter() { + output.extend(role.permissions.iter().cloned()) + } + + output + } + + fn permitted_tally(&self, + roles: &mut HashSet, + role_id: &String, + perm: &Permission + ) -> bool { + if let Some(role) = self.get(role_id) { + // Only check and tally parents of a role at the role itself if it's the first time we + // see it + if !roles.contains(role_id) { + for perm_rule in role.permissions.iter() { + if perm_rule.match_perm(perm) { + return true; + } + } + for parent in role.parents.iter() { + if self.permitted_tally(roles, parent, perm) { + return true; + } + } + + roles.insert(role_id.clone()); + } + } + + false + } + + pub fn is_permitted(&self, user: &UserData, perm: impl AsRef) -> bool { + let mut seen = HashSet::new(); + for role_id in user.roles.iter() { + if self.permitted_tally(&mut seen, role_id, perm.as_ref()) { + return true; + } + } + false + } } /// A "Role" from the Authorization perspective diff --git a/bffhd/capnp/user_system.rs b/bffhd/capnp/user_system.rs index 7408e85..ffce3b8 100644 --- a/bffhd/capnp/user_system.rs +++ b/bffhd/capnp/user_system.rs @@ -10,7 +10,7 @@ use api::usersystem_capnp::user_system::{ use crate::authorization::AuthorizationHandle; use crate::session::SessionHandle; -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Users { session: SessionHandle, } diff --git a/bffhd/config.rs b/bffhd/config.rs index 034739a..602a9a2 100644 --- a/bffhd/config.rs +++ b/bffhd/config.rs @@ -9,6 +9,7 @@ use std::net::{SocketAddr, IpAddr, ToSocketAddrs}; use std::str::FromStr; use serde::de::Error; use crate::authorization::permissions::{PermRule, PrivilegesBuf}; +use crate::authorization::roles::Role; type Result = std::result::Result; @@ -65,7 +66,7 @@ pub struct Config { pub db_path: PathBuf, pub auditlog_path: PathBuf, - pub roles: HashMap, + pub roles: HashMap, #[serde(flatten)] pub tlsconfig: TlsListen, @@ -86,14 +87,6 @@ impl Config { } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RoleConfig { - #[serde(default = "Vec::new")] - pub parents: Vec, - #[serde(default = "Vec::new")] - pub permissions: Vec, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModuleConfig { pub module: String, diff --git a/bffhd/resources/mod.rs b/bffhd/resources/mod.rs index 63d6422..79c6ed5 100644 --- a/bffhd/resources/mod.rs +++ b/bffhd/resources/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use futures_signals::signal::{Mutable, Signal, SignalExt}; use lmdb::RoTransaction; use rkyv::Archived; +use crate::authorization::permissions::PrivilegesBuf; use crate::config::MachineDescription; use crate::db::LMDBorrow; use crate::resources::modules::fabaccess::{MachineState, Status}; @@ -93,6 +94,10 @@ impl Resource { self.inner.signal() } + pub fn get_required_privs(&self) -> &PrivilegesBuf { + &self.inner.desc.privs + } + fn set_state(&self, state: MachineState) { self.inner.set_state(state) } diff --git a/bffhd/session/mod.rs b/bffhd/session/mod.rs index e39f513..4e0ccfb 100644 --- a/bffhd/session/mod.rs +++ b/bffhd/session/mod.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::Context; use lmdb::Environment; use once_cell::sync::OnceCell; -use crate::authorization::roles::Role; +use crate::authorization::roles::{Role, Roles}; use crate::resources::Resource; use crate::session::db::SessionCache; use crate::Users; @@ -16,10 +16,13 @@ static SESSION_CACHE: OnceCell = OnceCell::new(); #[derive(Clone)] pub struct SessionManager { users: Users, + roles: Roles, + + // cache: SessionCache // todo } impl SessionManager { - pub fn new(users: Users) -> Self { - Self { users } + pub fn new(users: Users, roles: Roles) -> Self { + Self { users, roles } } // TODO: make infallible @@ -27,33 +30,56 @@ impl SessionManager { let uid = uid.as_ref(); if let Some(user) = self.users.get_user(uid) { tracing::trace!(uid, "opening new session for user"); - Some(SessionHandle { user: UserRef::new(user.id) }) + Some(SessionHandle { + users: self.users.clone(), + roles: self.roles.clone(), + user: UserRef::new(user.id), + }) } else { None } } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct SessionHandle { + users: Users, + roles: Roles, + user: UserRef, } impl SessionHandle { pub fn get_user(&self) -> UserRef { - unimplemented!() + self.user.clone() } pub fn has_disclose(&self, resource: &Resource) -> bool { - unimplemented!() + if let Some(user) = self.users.get_user(self.user.get_username()) { + self.roles.is_permitted(&user.userdata, &resource.get_required_privs().disclose) + } else { + false + } } pub fn has_read(&self, resource: &Resource) -> bool { - unimplemented!() + if let Some(user) = self.users.get_user(self.user.get_username()) { + self.roles.is_permitted(&user.userdata, &resource.get_required_privs().read) + } else { + false + } } pub fn has_write(&self, resource: &Resource) -> bool { - unimplemented!() + if let Some(user) = self.users.get_user(self.user.get_username()) { + self.roles.is_permitted(&user.userdata, &resource.get_required_privs().write) + } else { + false + } } pub fn has_manage(&self, resource: &Resource) -> bool { - unimplemented!() + if let Some(user) = self.users.get_user(self.user.get_username()) { + self.roles.is_permitted(&user.userdata, &resource.get_required_privs().manage) + } else { + false + } } } \ No newline at end of file