From eec6c3b60cbaf6d0b1cbc576a5cc35a186e39495 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Wed, 20 Oct 2021 13:47:32 +0200 Subject: [PATCH] Add Permissions back --- src/api/auth.rs | 2 +- src/config.rs | 6 +- src/db/mod.rs | 2 - src/lib.rs | 1 + src/{db/access.rs => permissions.rs} | 227 +++++++-------------------- 5 files changed, 59 insertions(+), 179 deletions(-) rename src/{db/access.rs => permissions.rs} (67%) diff --git a/src/api/auth.rs b/src/api/auth.rs index eb43429..2daba86 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -1,6 +1,6 @@ //! Authentication subsystem //! -//! Authorization is over in `access.rs` +//! Authorization is over in `permissions` //! Authentication using SASL use std::sync::Arc; diff --git a/src/config.rs b/src/config.rs index f37d315..73299cd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,7 @@ use crate::error::Result; use std::fmt::Formatter; use std::net::{SocketAddr, IpAddr, ToSocketAddrs}; use std::str::FromStr; -use crate::db::access::PermRule; +use crate::permissions::{PermRule, RoleIdentifier}; use serde::de::Error; pub fn read(path: &Path) -> Result { @@ -41,13 +41,13 @@ pub struct Config { pub db_path: PathBuf, - pub roles: HashMap, + pub roles: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RoleConfig { #[serde(default = "Vec::new")] - pub parents: Vec, + pub parents: Vec, #[serde(default = "Vec::new")] pub permissions: Vec, } diff --git a/src/db/mod.rs b/src/db/mod.rs index 894c6a3..4e8984d 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -49,8 +49,6 @@ pub use resources::{ ResourceDB, }; -pub mod access; - use lmdb::Error; use rkyv::ser::serializers::AlignedSerializer; diff --git a/src/lib.rs b/src/lib.rs index f0323fb..98971c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ pub mod oid; mod varint; pub mod error; pub mod config; +mod permissions; /* diff --git a/src/db/access.rs b/src/permissions.rs similarity index 67% rename from src/db/access.rs rename to src/permissions.rs index a074c30..1fcf3cf 100644 --- a/src/db/access.rs +++ b/src/permissions.rs @@ -1,97 +1,10 @@ //! Access control logic //! -use slog::Logger; -use std::sync::Arc; - use std::fmt; -use std::collections::HashMap; use std::cmp::Ordering; use std::convert::{TryFrom, Into}; -use serde::{Serialize, Deserialize}; - -use crate::error::Result; - -use crate::config::Config; - -pub struct AccessControl { - internal: HashMap, -} - -pub fn init(_log: Logger, config: &Config, _env: Arc) - -> std::result::Result, crate::error::Error> -{ - Ok(config.roles.iter().map(|(name, cfg)| { - let id = RoleIdentifier::new(name, "internal"); - let parents = cfg.parents.iter().map(|n| RoleIdentifier::new(n, "internal")).collect(); - let role = Role::new(parents, cfg.permissions.clone()); - (id, role) - }).collect()) -} - -pub const ADMINPERM: &'static str = "bffh.admin"; -pub fn admin_perm() -> &'static Permission { - Permission::new(ADMINPERM) -} - -impl AccessControl { - pub fn new(internal: HashMap) -> Self { - Self { - internal, - } - } - - pub fn dump_roles(&self) -> Result> { - Ok(self.internal.iter().map(|(k,v)| (k.clone(), v.clone())).collect()) - } -} - -impl fmt::Debug for AccessControl { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut b = f.debug_struct("AccessControl"); - b.field("internal", &self.internal.get_type_name().to_string()); - b.finish() - } -} - -pub trait RoleDB { - fn get_type_name(&self) -> &'static str; - - fn get_role(&self, role_id: &RoleIdentifier) -> Result>; - - /// 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: &RoleIdentifier) -> Result<()> { - if let Some(role) = self.get_role(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); - } - } - - Ok(()) - } - -} - -impl RoleDB for HashMap { - fn get_type_name(&self) -> &'static str { - "Internal" - } - - fn get_role(&self, role_id: &RoleIdentifier) -> Result> { - Ok(self.get(role_id).cloned()) - } -} - /// A "Role" from the Authorization perspective /// /// You can think of a role as a bundle of permissions relating to other roles. In most cases a @@ -104,7 +17,7 @@ impl RoleDB for HashMap { /// of a machine; if later on a similar enough machine is put to use the administrator can just add /// the permission for that machine to an already existing role instead of manually having to /// assign to all users. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub struct Role { // If a role doesn't define parents, default to an empty Vec. #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -159,7 +72,7 @@ fn split_once(s: &str, split: char) -> Option<(&str, &str)> { .map(|idx| (&s[..idx], &s[(idx+1)..])) } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] #[serde(try_from = "String")] #[serde(into = "String")] /// Universal (relative) id of a role @@ -232,30 +145,11 @@ impl fmt::Display for RoleFromStrError { } } -#[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? -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), - } - } -} - fn is_sep_char(c: char) -> bool { c == '.' } -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] /// A set of privileges to a thing pub struct PrivilegesBuf { /// Which permission is required to know about the existance of this thing @@ -268,7 +162,7 @@ pub struct PrivilegesBuf { pub manage: PermissionBuf } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] #[repr(transparent)] #[serde(transparent)] /// An owned permission string @@ -309,7 +203,7 @@ impl PermissionBuf { self.inner.push_str(perm.as_str()) } - pub const fn from_string(inner: String) -> Self { + pub const fn from_string_unchecked(inner: String) -> Self { Self { inner } } @@ -320,11 +214,21 @@ impl PermissionBuf { pub fn into_string(self) -> String { self.inner } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} +impl AsRef for PermissionBuf { + #[inline(always)] + fn as_ref(&self) -> &String { + &self.inner + } } impl AsRef for PermissionBuf { #[inline(always)] fn as_ref(&self) -> &str { - &self.inner[..] + &self.inner.as_str() } } impl AsRef for PermissionBuf { @@ -345,19 +249,21 @@ impl fmt::Display for PermissionBuf { } } -#[repr(transparent)] #[derive(PartialEq, Eq, Hash, Debug)] +#[repr(transparent)] /// 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``` +/// but ```(bffh.perm) > (unrelated.but.more.specific.perm) == false```. +/// This allows to check if PermRule a grants Perm b by checking `a > b`. pub struct Permission { inner: str } impl Permission { pub fn new + ?Sized>(s: &S) -> &Permission { + // Safe because s is a valid reference unsafe { &*(s.as_ref() as *const str as *const Permission) } } @@ -390,7 +296,8 @@ impl PartialOrd for Permission { (None, None) => Some(Ordering::Equal), (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!"), + (Some(_), Some(_)) => unreachable!("Broken contract in Permission::partial_cmp: sides \ + should never be both Some!"), } } } @@ -402,7 +309,7 @@ impl AsRef for Permission { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] #[serde(try_from = "String")] #[serde(into = "String")] pub enum PermRule { @@ -420,8 +327,8 @@ pub enum PermRule { /// 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 + // 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. } @@ -478,13 +385,13 @@ impl TryFrom for PermRule { match &input[len-2..len] { ".+" => { input.truncate(len-2); - Ok(PermRule::Children(PermissionBuf::from_string(input))) + Ok(PermRule::Children(PermissionBuf::from_string_unchecked(input))) }, ".*" => { input.truncate(len-2); - Ok(PermRule::Subtree(PermissionBuf::from_string(input))) + Ok(PermRule::Subtree(PermissionBuf::from_string_unchecked(input))) }, - _ => Ok(PermRule::Base(PermissionBuf::from_string(input))), + _ => Ok(PermRule::Base(PermissionBuf::from_string_unchecked(input))), } } } @@ -496,25 +403,43 @@ mod tests { #[test] fn permission_ord_test() { - assert!(PermissionBuf::from_string("bffh.perm".to_string()) - > PermissionBuf::from_string("bffh.perm.sub".to_string())); + assert!(PermissionBuf::from_string_unchecked("bffh.perm".to_string()) + > PermissionBuf::from_string_unchecked("bffh.perm.sub".to_string())); } #[test] fn permission_simple_check_test() { - let perm = PermissionBuf::from_string("test.perm".to_string()); + let perm = PermissionBuf::from_string_unchecked("test.perm".to_string()); let rule = PermRule::Base(perm.clone()); assert!(rule.match_perm(&perm)); } #[test] - #[should_panic] fn permission_children_checks_only_children() { - let perm = PermissionBuf::from_string("test.perm".to_string()); + let perm = PermissionBuf::from_string_unchecked("test.perm".to_string()); let rule = PermRule::Children(perm.clone()); + assert_eq!(rule.match_perm(&perm), false); + + let perm2 = PermissionBuf::from_string_unchecked("test.perm.child".to_string()); + let perm3 = PermissionBuf::from_string_unchecked("test.perm.child.deeper".to_string()); + assert!(rule.match_perm(&perm2)); + assert!(rule.match_perm(&perm3)); + } + + #[test] + fn permission_subtree_checks_base() { + let perm = PermissionBuf::from_string_unchecked("test.perm".to_string()); + let rule = PermRule::Subtree(perm.clone()); + assert!(rule.match_perm(&perm)); + + let perm2 = PermissionBuf::from_string_unchecked("test.perm.child".to_string()); + let perm3 = PermissionBuf::from_string_unchecked("test.perm.child.deeper".to_string()); + + assert!(rule.match_perm(&perm2)); + assert!(rule.match_perm(&perm3)); } #[test] @@ -542,63 +467,19 @@ mod tests { } } - #[test] - fn load_examples_roles_test() { - let mut roles = Role::load_file("examples/roles.toml") - .expect("Couldn't load the example role defs. Does `examples/roles.toml` exist?"); - - let expected = vec![ - (RoleIdentifier { name: "anotherrole".to_string(), source: "lmdb".to_string() }, - Role { - parents: vec![], - permissions: vec![], - }), - (RoleIdentifier { name: "testrole".to_string(), source: "lmdb".to_string() }, - Role { - parents: vec![], - permissions: vec![ - PermRule::Subtree(PermissionBuf::from_string("lab.test".to_string())) - ], - }), - (RoleIdentifier { name: "somerole".to_string(), source: "lmdb".to_string() }, - Role { - parents: vec![ - RoleIdentifier::local_from_str("lmdb".to_string(), "testparent".to_string()), - ], - permissions: vec![ - PermRule::Base(PermissionBuf::from_string("lab.some.admin".to_string())) - ], - }), - (RoleIdentifier { name: "testparent".to_string(), source: "lmdb".to_string() }, - Role { - parents: vec![], - permissions: vec![ - PermRule::Base(PermissionBuf::from_string("lab.some.write".to_string())), - PermRule::Base(PermissionBuf::from_string("lab.some.read".to_string())), - PermRule::Base(PermissionBuf::from_string("lab.some.disclose".to_string())), - ], - }), - ]; - - for (id, role) in expected { - assert_eq!(roles.remove(&id).unwrap(), role); - } - - assert!(roles.is_empty()) - } #[test] fn rules_from_string_test() { assert_eq!( - PermRule::Base(PermissionBuf::from_string("bffh.perm".to_string())), + PermRule::Base(PermissionBuf::from_string_unchecked("bffh.perm".to_string())), PermRule::try_from("bffh.perm".to_string()).unwrap() ); assert_eq!( - PermRule::Children(PermissionBuf::from_string("bffh.perm".to_string())), + PermRule::Children(PermissionBuf::from_string_unchecked("bffh.perm".to_string())), PermRule::try_from("bffh.perm.+".to_string()).unwrap() ); assert_eq!( - PermRule::Subtree(PermissionBuf::from_string("bffh.perm".to_string())), + PermRule::Subtree(PermissionBuf::from_string_unchecked("bffh.perm".to_string())), PermRule::try_from("bffh.perm.*".to_string()).unwrap() ); }