diff --git a/src/db/access.rs b/src/db/access.rs index c7bd534..0865509 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -25,13 +25,60 @@ mod adapter_lmdb; use adapter_lmdb::PermissionsDB; pub use adapter_lmdb::init; -// FIXME: fabinfra/fabaccess/bffh#3 -pub type RoleIdentifier = u64; -pub type PermIdentifier = u64; - -pub trait AccessDB { - fn check(&self, userID: UserIdentifier, permID: PermIdentifier) -> Result; +pub trait RoleDB { 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) + } + + /// Check if a given permission is granted by any of the given roles or their respective + /// parents + /// + /// Default implementation which adapter may overwrite with more efficient specialized + /// implementations. + fn check_roles(&self, roles: &[RoleIdentifier], permID: PermIdentifier) -> Result { + // Tally all roles. Makes dependent roles easier + let mut roles = HashSet::new(); + for roleID in roles { + 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); + } + } + } + + return Ok(false); + } + + /// 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)? { + // 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)?; + } + + roles.insert(role); + } + } + + Ok(()) + } } /// A "Role" from the Authorization perspective @@ -62,7 +109,7 @@ pub struct Role { type SourceID = String; /// Universal (relative) id of a role -enum RoleID { +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 @@ -83,8 +130,179 @@ enum RoleID { impl fmt::Display for RoleID { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - RoleID::Local {name, source} => write!(f, "{}/{}@local", name, source), - RoleID::Remote {name, location} => write!(f, "{}@{}", name, location), + RoleIdentifier::Local {name, source} => write!(f, "{}/{}@local", name, source), + RoleIdentifier::Remote {name, location} => write!(f, "{}@{}", name, location), } } } + +#[derive(Debug, Clone, Serialize, Deserialize)] +/// An identifier for a permission +// XXX: Does remote permissions ever make sense? +// I mean we kinda get them for free so maybe? +pub enum PermIdentifier { + Local(PermRule), + Remote(PermRule, String), +} +impl fmt::Display for PermIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + PermIdentifier::Local(perm) + => write!(f, "{}", perm), + PermIdentifier::Remote(perm, source) + => write!(f, "{}@{}", perm, source), + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +#[repr(transparent)] +/// An owned permission string +/// +/// This is under the hood just a fancy std::String. +// TODO: What is the possible fallout from homograph attacks? +// i.e. "bffh.perm" is not the same as "bffհ.реrm" (Armenian 'հ':Հ and Cyrillic 'е':Е) +// See also https://util.unicode.org/UnicodeJsps/confusables.jsp +pub struct PermissionBuf { + inner: String, +} +impl PermissionBuf { + /// Allocate an empty `PermissionBuf` + pub fn new() -> Self { + PermissionBuf { inner: String::new() } + } + + /// Allocate a `PermissionBuf` with the given capacity given to the internal [`String`] + pub fn with_capacity() -> Self { + PermissionBuf { inner: String::with_capacity() } + } + + #[inline(always)] + pub fn as_permission(&self) -> &Permission { + self + } + + pub fn push>(&mut self, perm: P) { + self._push(perm.as_ref()) + } + + pub fn _push(&mut self, perm: &Permission) { + // in general we always need a separator unless the last byte is one or the string is empty + let need_sep = self.inner.chars().rev().next().map(|c| !is_sep_char(c)).unwrap_or(false); + if need_sep { + self.inner.push('.') + } + self.inner.push(perm.as_str()) + } + + pub fn from_string(inner: String) -> Self { + Self { inner } + } +} +impl AsRef for PermissionBuf { + #[inline(always)] + fn as_ref(&self) -> &Permission { + self.as_permission() + } +} + +#[repr(transparent)] +#[derive(PartialEq, Eq)] +/// A borrowed permission string +/// +/// Permissions have total equality and partial ordering. +/// Specifically permissions on the same path in a tree can be compared for specificity. +/// This means that ```(bffh.perm) > (bffh.perm.sub) == true``` +/// but ```(bffh.perm) > (unrelated.but.specific.perm) == false``` +pub struct Permission { + inner: str +} +impl Permission { + 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); + while { + l = self.next(); + r = other.next(); + + l.is_some() && r.is_some() + } { + if l.unwrap() != r.unwrap() { + return None; + } + } + + match (l,r) { + (None, None) => Some(Ordering::Equal), + (Some(_), None) => Some(Ordering::Lesser), + (None, Some(_)) => Some(Ordering::Greater), + (Some(_), Some(_)) => panic!("Broken contract in Permission::partial_cmp: sides should never be both Some!"), + } + } +} + + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermRule { + /// The permission is precise, + /// + /// i.e. `Base("bffh.perm")` grants bffh.perm but does not grant permission for bffh.perm.sub + Base(PermissionBuf), + /// The permissions is for the children of the node + /// + /// i.e. `Children("bffh.perm")` grants bffh.perm.sub, bffh.perm.sub.two *BUT NOT* bffh.perm + /// itself. + Children(PermissionBuf), + /// The permissions is for the subtree marked by the node + /// + /// i.e. `Children("bffh.perm")` grants bffh.perm.sub, bffh.perm.sub.two and also bffh.perm + /// itself. + Subtree(PermissionBuf), + // This lacks what LDAP calls ONELEVEL: The ability to grant the exact children but not several + // levels deep, i.e. Onelevel("bffh.perm") grants bffh.perm.sub *BUT NOT* bffh.perm.sub.two or + // bffh.perm itself. + // I can't think of a reason to use that so I'm skipping it for now. +} + +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, + } + } +} + +impl fmt::Display for PermRule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + PermRule::Base(perm) + => write!(f, "{}", perm), + PermRule::Children(parent) + => write!(f,"{}.+", parent), + PermRule::Subtree(parent) + => write!(f,"{}.*", parent), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn permission_ord_test() { + assert!(PermissionBuf::from_string("bffh.perm") > PermissionBuf::from_string("bffh.perm.sub")); + } +} diff --git a/src/db/access/adapter_lmdb.rs b/src/db/access/adapter_lmdb.rs index 8975eb7..f9638e8 100644 --- a/src/db/access/adapter_lmdb.rs +++ b/src/db/access/adapter_lmdb.rs @@ -16,7 +16,7 @@ use lmdb::{Environment, Transaction, RwTransaction, Cursor}; use crate::config::Settings; use crate::error::Result; -use crate::db::access::{PermIdentifier, Role, RoleIdentifier, AccessDB}; +use crate::db::access::{PermIdentifier, Role, RoleIdentifier, RoleDB}; use crate::db::user::{UserIdentifier, User}; #[derive(Clone, Debug)] @@ -34,7 +34,7 @@ impl PermissionsDB { /// Check if a given user has the given permission #[allow(unused)] - pub fn check(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result { + 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(); @@ -72,7 +72,7 @@ impl PermissionsDB { Ok(()) } - pub fn get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: RoleIdentifier) -> Result> { + pub fn _get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: RoleIdentifier) -> Result> { match txn.get(self.roledb, &roleID.to_ne_bytes()) { Ok(bytes) => { Ok(Some(flexbuffers::from_slice(bytes)?)) @@ -200,15 +200,15 @@ impl PermissionsDB { } } -impl AccessDB for Permissions { +impl RoleDB for PermissionsDB { fn check(&self, userID: UserIdentifier, permID: PermIdentifier) -> Result { let txn = self.env.begin_ro_txn()?; - self.inner.check(&txn, userID, permID) + self._check(&txn, userID, permID) } fn get_role(&self, roleID: RoleIdentifier) -> Result> { let txn = self.env.begin_ro_txn()?; - self.inner.get_role(&txn, roleID) + self._get_role(&txn, roleID) } } diff --git a/src/db/user.rs b/src/db/user.rs index 7f15bea..526ed1d 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -7,7 +7,6 @@ use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct User { /// The identification of this user. - #[serde(skip, default = get_uid)] pub id: UserIdentifier, /// A Person has N ≥ 0 roles. @@ -40,10 +39,6 @@ impl UserIdentifier { } } -fn get_uid() -> UserIdentifier { - -} - impl fmt::Display for UserIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let r = write!(f, "{}", self.uid); diff --git a/src/network.rs b/src/network.rs index a93b8b0..da14434 100644 --- a/src/network.rs +++ b/src/network.rs @@ -2,6 +2,7 @@ use futures_signals::signal::Signal; use crate::machine; use crate::access; +use crate::db::user::UserIdentifier; struct Network {