diff --git a/src/db/access.rs b/src/db/access.rs index 0865509..100b4ae 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -3,6 +3,7 @@ use std::fmt; use std::collections::HashSet; +use std::cmp::Ordering; use std::convert::TryInto; @@ -22,18 +23,19 @@ use crate::error::Result; mod adapter_lmdb; +use crate::db::user::User; use adapter_lmdb::PermissionsDB; pub use adapter_lmdb::init; pub trait RoleDB { - fn get_role(&self, roleID: RoleIdentifier) -> Result>; + fn get_role(&self, roleID: &RoleIdentifier) -> Result>; /// Check if a given user has the given permission /// /// Default implementation which adapter may overwrite with more efficient specialized /// implementations. - fn check(&self, user: &User, permID: PermIdentifier) -> Result { - self.check_roles(user.roles) + fn check(&self, user: &User, permID: &PermIdentifier) -> Result { + self.check_roles(&user.roles, permID) } /// Check if a given permission is granted by any of the given roles or their respective @@ -41,18 +43,17 @@ pub trait RoleDB { /// /// Default implementation which adapter may overwrite with more efficient specialized /// implementations. - fn check_roles(&self, roles: &[RoleIdentifier], permID: PermIdentifier) -> Result { + fn check_roles(&self, roles: &[RoleIdentifier], permID: &PermIdentifier) -> Result { // Tally all roles. Makes dependent roles easier - let mut roles = HashSet::new(); + let mut roleset = HashSet::new(); for roleID in roles { - self.tally_role(txn, &mut roles, roleID)?; + self.tally_role(&mut roleset, roleID)?; } // Iter all unique role->permissions we've found and early return on match. - // TODO: Change this for negative permissions? - for role in roles.iter() { + for role in roleset.iter() { for perm in role.permissions.iter() { - if permID == *perm { + if permID == perm { return Ok(true); } } @@ -64,13 +65,13 @@ pub trait RoleDB { /// Tally a role dependency tree into a set /// /// Default implementation which adapter may overwrite with more efficient implementations - fn tally_role(&self, roles: &mut HashSet, roleID: RoleIdentifier) -> Result<()> { - if let Some(role) = self.get_role(txn, roleID)? { + fn tally_role(&self, roles: &mut HashSet, roleID: &RoleIdentifier) -> Result<()> { + if let Some(role) = self.get_role(roleID)? { // 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) { for parent in role.parents.iter() { - self.tally_role(txn, roles, *parent)?; + self.tally_role(roles, parent)?; } roles.insert(role); @@ -108,8 +109,15 @@ pub struct Role { type SourceID = String; +fn split_once(s: &str, split: char) -> Option<(&str, &str)> { + s + .find(split) + .map(|idx| s.split_at(idx)) +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] /// Universal (relative) id of a role -enum RoleIdentifier { +pub enum RoleIdentifier { /// The role comes from this instance Local { /// Locally unique name for the role. No other role at this instance no matter the source @@ -127,16 +135,35 @@ enum RoleIdentifier { location: String, } } -impl fmt::Display for RoleID { +impl std::str::FromStr for RoleIdentifier { + type Err = RoleFromStrError; + + fn from_str(s: &str) -> std::result::Result { + if let Some((name, location)) = split_once(s, '@') { + Ok(RoleIdentifier::Remote { name: name.to_string(), location: location.to_string() }) + } else if let Some((name, source)) = split_once(s, '%') { + Ok(RoleIdentifier::Local { name: name.to_string(), source: source.to_string() }) + } else { + Err(RoleFromStrError::Invalid) + } + } +} +impl fmt::Display for RoleIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { + match self { RoleIdentifier::Local {name, source} => write!(f, "{}/{}@local", name, source), RoleIdentifier::Remote {name, location} => write!(f, "{}@{}", name, location), } } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum RoleFromStrError { + /// No '@' or '%' found. That's strange, huh? + Invalid +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] /// An identifier for a permission // XXX: Does remote permissions ever make sense? // I mean we kinda get them for free so maybe? @@ -146,7 +173,7 @@ pub enum PermIdentifier { } impl fmt::Display for PermIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { + match self { PermIdentifier::Local(perm) => write!(f, "{}", perm), PermIdentifier::Remote(perm, source) @@ -155,7 +182,11 @@ impl fmt::Display for PermIdentifier { } } -#[derive(Clone, Serialize, Deserialize)] +fn is_sep_char(c: char) -> bool { + c == '.' +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(transparent)] /// An owned permission string /// @@ -173,13 +204,13 @@ impl PermissionBuf { } /// Allocate a `PermissionBuf` with the given capacity given to the internal [`String`] - pub fn with_capacity() -> Self { - PermissionBuf { inner: String::with_capacity() } + pub fn with_capacity(cap: usize) -> Self { + PermissionBuf { inner: String::with_capacity(cap) } } #[inline(always)] pub fn as_permission(&self) -> &Permission { - self + self.as_ref() } pub fn push>(&mut self, perm: P) { @@ -192,22 +223,39 @@ impl PermissionBuf { if need_sep { self.inner.push('.') } - self.inner.push(perm.as_str()) + self.inner.push_str(perm.as_str()) } pub fn from_string(inner: String) -> Self { Self { inner } } } -impl AsRef for PermissionBuf { +impl AsRef for PermissionBuf { #[inline(always)] + fn as_ref(&self) -> &str { + &self.inner[..] + } +} +impl AsRef for PermissionBuf { + #[inline] fn as_ref(&self) -> &Permission { - self.as_permission() + Permission::new(self) + } +} +impl PartialOrd for PermissionBuf { + fn partial_cmp(&self, other: &Self) -> Option { + let a: &Permission = self.as_ref(); + a.partial_cmp(other.as_ref()) + } +} +impl fmt::Display for PermissionBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) } } #[repr(transparent)] -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Hash)] /// A borrowed permission string /// /// Permissions have total equality and partial ordering. @@ -218,21 +266,27 @@ pub struct Permission { inner: str } impl Permission { - pub fn as_str(&self) -> &str { - self.inner + pub fn new + ?Sized>(s: &S) -> &Permission { + unsafe { &*(s.as_ref() as *const str as *const Permission) } } - pub fn iter(&self) -> std::str::Split { + pub fn as_str(&self) -> &str { + &self.inner + } + + pub fn iter(&self) -> std::str::Split { self.inner.split('.') } } impl PartialOrd for Permission { fn partial_cmp(&self, other: &Permission) -> Option { - let (l,r) = (None, None); + let mut i = self.iter(); + let mut j = other.iter(); + let (mut l, mut r) = (None, None); while { - l = self.next(); - r = other.next(); + l = i.next(); + r = j.next(); l.is_some() && r.is_some() } { @@ -243,7 +297,7 @@ impl PartialOrd for Permission { match (l,r) { (None, None) => Some(Ordering::Equal), - (Some(_), None) => Some(Ordering::Lesser), + (Some(_), None) => Some(Ordering::Less), (None, Some(_)) => Some(Ordering::Greater), (Some(_), Some(_)) => panic!("Broken contract in Permission::partial_cmp: sides should never be both Some!"), } @@ -251,7 +305,7 @@ impl PartialOrd for Permission { } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum PermRule { /// The permission is precise, /// @@ -277,16 +331,16 @@ impl PermRule { // Does this rule match that permission fn match_perm>(rule: &PermRule, perm: P) -> bool { match rule { - Base(base) => base == perm, - Children(parent) => parent > perm , - Subtree(parent) => parent >= perm, + PermRule::Base(base) => base.as_permission() == perm.as_ref(), + PermRule::Children(parent) => parent.as_permission() > perm.as_ref() , + PermRule::Subtree(parent) => parent.as_permission() >= perm.as_ref(), } } } impl fmt::Display for PermRule { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { + match self { PermRule::Base(perm) => write!(f, "{}", perm), PermRule::Children(parent) diff --git a/src/db/access/adapter_lmdb.rs b/src/db/access/adapter_lmdb.rs index f9638e8..fcf960a 100644 --- a/src/db/access/adapter_lmdb.rs +++ b/src/db/access/adapter_lmdb.rs @@ -34,21 +34,19 @@ impl PermissionsDB { /// Check if a given user has the given permission #[allow(unused)] - pub fn _check(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result { - if let Some(user) = self.get_user(txn, userID)? { - // Tally all roles. Makes dependent roles easier - let mut roles = HashSet::new(); - for roleID in user.roles { - self.tally_role(txn, &mut roles, roleID)?; - } + pub fn _check(&self, txn: &T, user: &User, permID: &PermIdentifier) -> Result { + // Tally all roles. Makes dependent roles easier + let mut roles = HashSet::new(); + for roleID in user.roles.iter() { + self._tally_role(txn, &mut roles, roleID)?; + } - // Iter all unique role->permissions we've found and early return on match. - // TODO: Change this for negative permissions? - for role in roles.iter() { - for perm in role.permissions.iter() { - if permID == *perm { - return Ok(true); - } + // Iter all unique role->permissions we've found and early return on match. + // TODO: Change this for negative permissions? + for role in roles.iter() { + for perm in role.permissions.iter() { + if permID == perm { + return Ok(true); } } } @@ -56,13 +54,13 @@ impl PermissionsDB { return Ok(false); } - fn tally_role(&self, txn: &T, roles: &mut HashSet, roleID: RoleIdentifier) -> Result<()> { - if let Some(role) = self.get_role(txn, roleID)? { + fn _tally_role(&self, txn: &T, roles: &mut HashSet, roleID: &RoleIdentifier) -> Result<()> { + if let Some(role) = self._get_role(txn, roleID)? { // 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) { for parent in role.parents.iter() { - self.tally_role(txn, roles, *parent)?; + self._tally_role(txn, roles, parent)?; } roles.insert(role); @@ -72,8 +70,9 @@ impl PermissionsDB { Ok(()) } - pub fn _get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: RoleIdentifier) -> Result> { - match txn.get(self.roledb, &roleID.to_ne_bytes()) { + pub fn _get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: &RoleIdentifier) -> Result> { + let string = format!("{}", roleID); + match txn.get(self.roledb, &string.as_bytes()) { Ok(bytes) => { Ok(Some(flexbuffers::from_slice(bytes)?)) }, @@ -82,9 +81,10 @@ impl PermissionsDB { } } - fn put_role(&self, txn: &mut RwTransaction, roleID: RoleIdentifier, role: Role) -> Result<()> { + fn put_role(&self, txn: &mut RwTransaction, roleID: &RoleIdentifier, role: Role) -> Result<()> { let bytes = flexbuffers::to_vec(role)?; - txn.put(self.roledb, &roleID.to_ne_bytes(), &bytes, lmdb::WriteFlags::empty())?; + let string = format!("{}", roleID); + txn.put(self.roledb, &string.as_bytes(), &bytes, lmdb::WriteFlags::empty())?; Ok(()) } @@ -164,10 +164,10 @@ impl PermissionsDB { let roleID_str = path .file_stem().expect("Found a file with no filename?") .to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!"); - let roleID = match u64::from_str_radix(roleID_str, 16) { + let roleID = match str::parse(roleID_str) { Ok(i) => i, Err(e) => { - warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e); + warn!(self.log, "File {} had a invalid name.", path.display()); continue; } }; @@ -189,8 +189,8 @@ impl PermissionsDB { continue; } }; - self.put_role(txn, roleID, role)?; - debug!(self.log, "Loaded role {}", roleID); + self.put_role(txn, &roleID, role)?; + debug!(self.log, "Loaded role {}", &roleID); } else { warn!(self.log, "Path {} is not a file, skipping!", path.display()); } @@ -201,21 +201,28 @@ impl PermissionsDB { } impl RoleDB for PermissionsDB { - fn check(&self, userID: UserIdentifier, permID: PermIdentifier) -> Result { + fn check(&self, user: &User, permID: &PermIdentifier) -> Result { let txn = self.env.begin_ro_txn()?; - self._check(&txn, userID, permID) + self._check(&txn, user, permID) } - fn get_role(&self, roleID: RoleIdentifier) -> Result> { + fn get_role(&self, roleID: &RoleIdentifier) -> Result> { let txn = self.env.begin_ro_txn()?; self._get_role(&txn, roleID) } + + fn tally_role(&self, roles: &mut HashSet, roleID: &RoleIdentifier) -> Result<()> { + let txn = self.env.begin_ro_txn()?; + self._tally_role(&txn, roles, roleID) + } } /// Initialize the access db by loading all the lmdb databases -pub fn init(log: Logger, config: &Settings, env: Arc) -> std::result::Result { +pub fn init(log: Logger, config: &Settings, env: Arc) + -> std::result::Result +{ let mut flags = lmdb::DatabaseFlags::empty(); flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); let roledb = env.create_db(Some("role"), flags)?; diff --git a/src/db/machine.rs b/src/db/machine.rs index 359e280..1b2f089 100644 --- a/src/db/machine.rs +++ b/src/db/machine.rs @@ -16,6 +16,8 @@ use crate::error::Result; use crate::config::Settings; use crate::access; +use crate::db::user::UserIdentifier; + use capnp::Error; use uuid::Uuid; @@ -27,6 +29,7 @@ use smol::channel::{Receiver, Sender}; use futures_signals::signal::*; use crate::registries::StatusSignal; +use crate::db::user::User; pub type ID = Uuid; @@ -139,14 +142,13 @@ impl Machine { /// Requests to use a machine. Returns `true` if successful. /// /// This will update the internal state of the machine, notifying connected actors, if any. - pub fn request_use + pub fn request_use ( &mut self - , txn: &T - , pp: &access::PermissionsDB - , who: access::UserIdentifier + , pp: &P + , who: &User ) -> Result { - if pp.check(txn, who, self.perm)? { + if pp.check(who, &self.perm)? { self.state.set(Status::Occupied); return Ok(true); } else { diff --git a/src/db/user.rs b/src/db/user.rs index 526ed1d..3986c8e 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -20,7 +20,7 @@ pub struct User { /// Locally unique identifier for an user -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct UserIdentifier { /// Main UID. Must be unique in this instance so that the tuple (uid, location) is globally /// unique. @@ -42,10 +42,10 @@ impl UserIdentifier { impl fmt::Display for UserIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let r = write!(f, "{}", self.uid); - if let Some(s) = self.subuid { + if let Some(ref s) = self.subuid { write!(f, "+{}", s)?; } - if let Some(l) = self.location { + if let Some(ref l) = self.location { write!(f, "@{}", l)?; } r @@ -70,10 +70,10 @@ mod tests { assert_eq!("testuser", format!("{}", UserIdentifier::new(uid, None, None))); assert_eq!("testuser+testsuid", - format!(UserIdentifier::new("testuser", Some(suid), None))); + format!("{}", UserIdentifier::new(uid, Some(suid), None))); assert_eq!("testuser+testsuid", - format!(UserIdentifier::new("testuser", Some(suid), None))); + format!("{}", UserIdentifier::new(uid, Some(suid), None))); assert_eq!("testuser+testsuid@testloc", - format!(UserIdentifier::new("testuser", Some(suid), Some(location)))); + format!("{}", UserIdentifier::new(uid, Some(suid), Some(location)))); } } diff --git a/src/main.rs b/src/main.rs index 2a94ea2..9698202 100644 --- a/src/main.rs +++ b/src/main.rs @@ -163,7 +163,7 @@ fn main() -> Result<(), Error> { let mut txn = env.begin_rw_txn()?; let path = path.to_path_buf(); - pdb?.inner.load_db(&mut txn, path.clone())?; + pdb?.load_db(&mut txn, path.clone())?; mdb?.load_db(&mut txn, path)?; txn.commit(); } else { @@ -181,7 +181,7 @@ fn main() -> Result<(), Error> { let txn = env.begin_ro_txn()?; let path = path.to_path_buf(); - pdb?.inner.dump_db(&txn, path.clone())?; + pdb?.dump_db(&txn, path.clone())?; mdb?.dump_db(&txn, path)?; } else { error!(log, "You must provide a directory path to dump into"); diff --git a/src/network.rs b/src/network.rs index da14434..bb69984 100644 --- a/src/network.rs +++ b/src/network.rs @@ -35,5 +35,5 @@ impl Network { enum Event { /// An user wants to use a machine // TODO: Define /what/ an user wants to do with said machine? - MachineRequest(machine::ID, access::UserIdentifier), + MachineRequest(machine::ID, UserIdentifier), }