Add Permissions back

This commit is contained in:
Nadja Reitzenstein 2021-10-20 13:47:32 +02:00
parent 541f8585c0
commit eec6c3b60c
5 changed files with 59 additions and 179 deletions

View File

@ -1,6 +1,6 @@
//! Authentication subsystem //! Authentication subsystem
//! //!
//! Authorization is over in `access.rs` //! Authorization is over in `permissions`
//! Authentication using SASL //! Authentication using SASL
use std::sync::Arc; use std::sync::Arc;

View File

@ -8,7 +8,7 @@ use crate::error::Result;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::net::{SocketAddr, IpAddr, ToSocketAddrs}; use std::net::{SocketAddr, IpAddr, ToSocketAddrs};
use std::str::FromStr; use std::str::FromStr;
use crate::db::access::PermRule; use crate::permissions::{PermRule, RoleIdentifier};
use serde::de::Error; use serde::de::Error;
pub fn read(path: &Path) -> Result<Config> { pub fn read(path: &Path) -> Result<Config> {
@ -41,13 +41,13 @@ pub struct Config {
pub db_path: PathBuf, pub db_path: PathBuf,
pub roles: HashMap<String, RoleConfig>, pub roles: HashMap<RoleIdentifier, RoleConfig>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoleConfig { pub struct RoleConfig {
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
pub parents: Vec<String>, pub parents: Vec<RoleIdentifier>,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
pub permissions: Vec<PermRule>, pub permissions: Vec<PermRule>,
} }

View File

@ -49,8 +49,6 @@ pub use resources::{
ResourceDB, ResourceDB,
}; };
pub mod access;
use lmdb::Error; use lmdb::Error;
use rkyv::ser::serializers::AlignedSerializer; use rkyv::ser::serializers::AlignedSerializer;

View File

@ -22,6 +22,7 @@ pub mod oid;
mod varint; mod varint;
pub mod error; pub mod error;
pub mod config; pub mod config;
mod permissions;
/* /*

View File

@ -1,97 +1,10 @@
//! Access control logic //! Access control logic
//! //!
use slog::Logger;
use std::sync::Arc;
use std::fmt; use std::fmt;
use std::collections::HashMap;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::convert::{TryFrom, Into}; use std::convert::{TryFrom, Into};
use serde::{Serialize, Deserialize};
use crate::error::Result;
use crate::config::Config;
pub struct AccessControl {
internal: HashMap<RoleIdentifier, Role>,
}
pub fn init(_log: Logger, config: &Config, _env: Arc<lmdb::Environment>)
-> std::result::Result<HashMap<RoleIdentifier, Role>, 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<RoleIdentifier, Role>) -> Self {
Self {
internal,
}
}
pub fn dump_roles(&self) -> Result<Vec<(RoleIdentifier, Role)>> {
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<Option<Role>>;
/// 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<RoleIdentifier, Role>, 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<RoleIdentifier, Role> {
fn get_type_name(&self) -> &'static str {
"Internal"
}
fn get_role(&self, role_id: &RoleIdentifier) -> Result<Option<Role>> {
Ok(self.get(role_id).cloned())
}
}
/// A "Role" from the Authorization perspective /// 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 /// 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<RoleIdentifier, Role> {
/// of a machine; if later on a similar enough machine is put to use the administrator can just add /// 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 /// the permission for that machine to an already existing role instead of manually having to
/// assign to all users. /// 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 { pub struct Role {
// If a role doesn't define parents, default to an empty Vec. // If a role doesn't define parents, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[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)..])) .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(try_from = "String")]
#[serde(into = "String")] #[serde(into = "String")]
/// Universal (relative) id of a role /// 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 { fn is_sep_char(c: char) -> bool {
c == '.' c == '.'
} }
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
/// A set of privileges to a thing /// A set of privileges to a thing
pub struct PrivilegesBuf { pub struct PrivilegesBuf {
/// Which permission is required to know about the existance of this thing /// Which permission is required to know about the existance of this thing
@ -268,7 +162,7 @@ pub struct PrivilegesBuf {
pub manage: PermissionBuf pub manage: PermissionBuf
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[repr(transparent)] #[repr(transparent)]
#[serde(transparent)] #[serde(transparent)]
/// An owned permission string /// An owned permission string
@ -309,7 +203,7 @@ impl PermissionBuf {
self.inner.push_str(perm.as_str()) 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 } Self { inner }
} }
@ -320,11 +214,21 @@ impl PermissionBuf {
pub fn into_string(self) -> String { pub fn into_string(self) -> String {
self.inner self.inner
} }
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl AsRef<String> for PermissionBuf {
#[inline(always)]
fn as_ref(&self) -> &String {
&self.inner
}
} }
impl AsRef<str> for PermissionBuf { impl AsRef<str> for PermissionBuf {
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
&self.inner[..] &self.inner.as_str()
} }
} }
impl AsRef<Permission> for PermissionBuf { impl AsRef<Permission> for PermissionBuf {
@ -345,19 +249,21 @@ impl fmt::Display for PermissionBuf {
} }
} }
#[repr(transparent)]
#[derive(PartialEq, Eq, Hash, Debug)] #[derive(PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
/// A borrowed permission string /// A borrowed permission string
/// ///
/// Permissions have total equality and partial ordering. /// Permissions have total equality and partial ordering.
/// Specifically permissions on the same path in a tree can be compared for specificity. /// Specifically permissions on the same path in a tree can be compared for specificity.
/// This means that ```(bffh.perm) > (bffh.perm.sub) == true``` /// 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 { pub struct Permission {
inner: str inner: str
} }
impl Permission { impl Permission {
pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Permission { pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Permission {
// Safe because s is a valid reference
unsafe { &*(s.as_ref() as *const str as *const Permission) } unsafe { &*(s.as_ref() as *const str as *const Permission) }
} }
@ -390,7 +296,8 @@ impl PartialOrd for Permission {
(None, None) => Some(Ordering::Equal), (None, None) => Some(Ordering::Equal),
(Some(_), None) => Some(Ordering::Less), (Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater), (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<Permission> 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(try_from = "String")]
#[serde(into = "String")] #[serde(into = "String")]
pub enum PermRule { 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 /// i.e. `Children("bffh.perm")` grants bffh.perm.sub, bffh.perm.sub.two and also bffh.perm
/// itself. /// itself.
Subtree(PermissionBuf), Subtree(PermissionBuf),
// This lacks what LDAP calls ONELEVEL: The ability to grant the exact children but not several // 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 // levels deep, i.e. `Onelevel("bffh.perm")` grants bffh.perm.sub *BUT NOT* bffh.perm.sub.two or
// bffh.perm itself. // bffh.perm itself.
// I can't think of a reason to use that so I'm skipping it for now. // I can't think of a reason to use that so I'm skipping it for now.
} }
@ -478,13 +385,13 @@ impl TryFrom<String> for PermRule {
match &input[len-2..len] { match &input[len-2..len] {
".+" => { ".+" => {
input.truncate(len-2); input.truncate(len-2);
Ok(PermRule::Children(PermissionBuf::from_string(input))) Ok(PermRule::Children(PermissionBuf::from_string_unchecked(input)))
}, },
".*" => { ".*" => {
input.truncate(len-2); 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] #[test]
fn permission_ord_test() { fn permission_ord_test() {
assert!(PermissionBuf::from_string("bffh.perm".to_string()) assert!(PermissionBuf::from_string_unchecked("bffh.perm".to_string())
> PermissionBuf::from_string("bffh.perm.sub".to_string())); > PermissionBuf::from_string_unchecked("bffh.perm.sub".to_string()));
} }
#[test] #[test]
fn permission_simple_check_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()); let rule = PermRule::Base(perm.clone());
assert!(rule.match_perm(&perm)); assert!(rule.match_perm(&perm));
} }
#[test] #[test]
#[should_panic]
fn permission_children_checks_only_children() { 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()); 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)); 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] #[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] #[test]
fn rules_from_string_test() { fn rules_from_string_test() {
assert_eq!( 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() PermRule::try_from("bffh.perm".to_string()).unwrap()
); );
assert_eq!( 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() PermRule::try_from("bffh.perm.+".to_string()).unwrap()
); );
assert_eq!( 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() PermRule::try_from("bffh.perm.*".to_string()).unwrap()
); );
} }