fabaccess-bffh/bffhd/permissions.rs

506 lines
16 KiB
Rust
Raw Normal View History

2020-02-14 12:20:17 +01:00
//! Access control logic
//!
2020-10-26 12:58:55 +01:00
use std::fmt;
2020-10-28 23:24:02 +01:00
use std::cmp::Ordering;
use std::convert::{TryFrom, Into};
2020-10-23 16:35:10 +02:00
/// 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
/// role represents a real-world education or apprenticeship, which gives a person the education
/// necessary to use a machine safely.
/// Roles are assigned permissions which in most cases evaluate to granting a person the right to
/// use certain (potentially) dangerous machines.
/// Using this indirection makes administration easier in certain ways; instead of maintaining
/// permissions on users directly the user is given a role after having been educated on the safety
/// 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.
2021-10-20 13:47:32 +02:00
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
2020-10-26 12:58:55 +01:00
pub struct Role {
// If a role doesn't define parents, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
2020-10-23 16:35:10 +02:00
/// A Role can have parents, inheriting all permissions
///
/// This makes situations where different levels of access are required easier: Each higher
/// level of access sets the lower levels of access as parent, inheriting their permission; if
/// you are allowed to manage a machine you are then also allowed to use it and so on
parents: Vec<RoleIdentifier>,
// If a role doesn't define permissions, default to an empty Vec.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
2020-11-17 13:40:44 +01:00
permissions: Vec<PermRule>,
2020-10-23 16:35:10 +02:00
}
impl Role {
2021-09-21 07:48:19 +02:00
pub fn new(parents: Vec<RoleIdentifier>, permissions: Vec<PermRule>) -> Self {
Self { parents, permissions }
}
}
2021-09-19 19:47:29 +02:00
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "parents:")?;
if self.parents.is_empty() {
writeln!(f, " []")?;
} else {
writeln!(f, "")?;
for p in self.parents.iter() {
writeln!(f, " - {}", p)?;
}
}
write!(f, "permissions:")?;
if self.permissions.is_empty() {
writeln!(f, " []")?;
} else {
writeln!(f, "")?;
for p in self.permissions.iter() {
writeln!(f, " - {}", p)?;
}
}
Ok(())
}
}
2020-10-26 12:58:55 +01:00
type SourceID = String;
2020-10-28 23:24:02 +01:00
fn split_once(s: &str, split: char) -> Option<(&str, &str)> {
s
.find(split)
2021-01-27 15:40:33 +01:00
.map(|idx| (&s[..idx], &s[(idx+1)..]))
2020-10-28 23:24:02 +01:00
}
2021-10-20 13:47:32 +02:00
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(try_from = "String")]
2021-01-27 15:40:33 +01:00
#[serde(into = "String")]
2020-10-26 12:58:55 +01:00
/// Universal (relative) id of a role
2021-01-27 15:40:33 +01:00
pub struct RoleIdentifier {
/// Locally unique name for the role. No other role at this instance no matter the source
/// may have the same name
name: String,
/// Role Source, i.e. the database the role comes from
source: SourceID,
2020-10-26 12:58:55 +01:00
}
2021-09-21 07:48:19 +02:00
impl RoleIdentifier {
pub fn new<>(name: &str, source: &str) -> Self {
Self { name: name.to_string(), source: source.to_string() }
}
pub fn from_strings(name: String, source: String) -> Self {
Self { name, source }
}
}
impl fmt::Display for RoleIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2021-10-20 18:37:50 +02:00
if self.source != "" {
write!(f, "{}/{}", self.name, self.source)
} else {
write!(f, "{}", self.name)
}
}
}
2020-10-28 23:24:02 +01:00
impl std::str::FromStr for RoleIdentifier {
type Err = RoleFromStrError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2021-01-27 15:40:33 +01:00
if let Some((name, source)) = split_once(s, '/') {
Ok(RoleIdentifier { name: name.to_string(), source: source.to_string() })
2020-10-28 23:24:02 +01:00
} else {
2021-10-20 18:37:50 +02:00
Ok(RoleIdentifier { name: s.to_string(), source: String::new() })
2020-10-28 23:24:02 +01:00
}
}
}
impl TryFrom<String> for RoleIdentifier {
type Error = RoleFromStrError;
fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
2021-01-27 15:40:33 +01:00
<RoleIdentifier as std::str::FromStr>::from_str(&s)
}
}
impl Into<String> for RoleIdentifier {
fn into(self) -> String {
format!("{}", self)
2020-09-10 12:32:33 +02:00
}
2020-09-10 10:39:46 +02:00
}
2020-10-28 19:22:11 +01:00
impl RoleIdentifier {
pub fn local_from_str(source: String, name: String) -> Self {
2021-01-27 15:40:33 +01:00
RoleIdentifier { name, source }
}
}
2020-10-28 23:24:02 +01:00
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum RoleFromStrError {
/// No '@' or '%' found. That's strange, huh?
Invalid
}
impl fmt::Display for RoleFromStrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RoleFromStrError::Invalid
=> write!(f, "Rolename are of form 'name%source' or 'name@realm'."),
}
}
}
2020-10-28 23:24:02 +01:00
fn is_sep_char(c: char) -> bool {
c == '.'
}
2021-10-20 13:47:32 +02:00
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
2020-11-10 14:56:28 +01:00
/// A set of privileges to a thing
pub struct PrivilegesBuf {
/// Which permission is required to know about the existance of this thing
2020-11-17 13:40:44 +01:00
pub disclose: PermissionBuf,
2020-11-10 14:56:28 +01:00
/// Which permission is required to read this thing
2020-11-17 13:40:44 +01:00
pub read: PermissionBuf,
2020-11-10 14:56:28 +01:00
/// Which permission is required to write parts of this thing
2020-11-17 13:40:44 +01:00
pub write: PermissionBuf,
2020-11-10 14:56:28 +01:00
/// Which permission is required to manage all parts of this thing
2020-11-17 13:40:44 +01:00
pub manage: PermissionBuf
2020-11-10 14:56:28 +01:00
}
2021-10-20 13:47:32 +02:00
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
2020-10-28 19:22:11 +01:00
#[repr(transparent)]
#[serde(transparent)]
2020-10-28 19:22:11 +01:00
/// 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 {
2021-10-20 14:11:56 +02:00
#[inline(always)]
2020-10-28 19:22:11 +01:00
/// Allocate an empty `PermissionBuf`
pub fn new() -> Self {
PermissionBuf { inner: String::new() }
}
2021-10-20 14:11:56 +02:00
#[inline(always)]
2020-10-28 19:22:11 +01:00
/// Allocate a `PermissionBuf` with the given capacity given to the internal [`String`]
2020-10-28 23:24:02 +01:00
pub fn with_capacity(cap: usize) -> Self {
PermissionBuf { inner: String::with_capacity(cap) }
2020-10-28 19:22:11 +01:00
}
#[inline(always)]
pub fn as_permission(&self) -> &Permission {
2020-10-28 23:24:02 +01:00
self.as_ref()
2020-10-28 19:22:11 +01:00
}
pub fn push<P: AsRef<Permission>>(&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('.')
}
2020-10-28 23:24:02 +01:00
self.inner.push_str(perm.as_str())
2020-10-28 19:22:11 +01:00
}
2021-10-20 14:11:56 +02:00
#[inline(always)]
2021-10-20 13:47:32 +02:00
pub const fn from_string_unchecked(inner: String) -> Self {
2020-10-28 19:22:11 +01:00
Self { inner }
}
2021-10-20 14:11:56 +02:00
#[inline]
2020-11-24 14:16:22 +01:00
pub fn from_perm(perm: &Permission) -> Self {
2021-10-20 14:11:56 +02:00
Self { inner: perm.as_str().to_string() }
2020-11-24 14:16:22 +01:00
}
2021-10-20 14:11:56 +02:00
#[inline(always)]
pub fn into_string(self) -> String {
self.inner
}
2021-10-20 13:47:32 +02:00
2021-10-20 14:11:56 +02:00
#[inline(always)]
2021-10-20 13:47:32 +02:00
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl AsRef<String> for PermissionBuf {
#[inline(always)]
fn as_ref(&self) -> &String {
&self.inner
}
2020-10-28 19:22:11 +01:00
}
2020-10-28 23:24:02 +01:00
impl AsRef<str> for PermissionBuf {
2020-10-28 19:22:11 +01:00
#[inline(always)]
2020-10-28 23:24:02 +01:00
fn as_ref(&self) -> &str {
2021-10-20 13:47:32 +02:00
&self.inner.as_str()
2020-10-28 23:24:02 +01:00
}
}
impl AsRef<Permission> for PermissionBuf {
#[inline]
2020-10-28 19:22:11 +01:00
fn as_ref(&self) -> &Permission {
2020-10-28 23:24:02 +01:00
Permission::new(self)
}
}
impl PartialOrd for PermissionBuf {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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)
2020-10-28 19:22:11 +01:00
}
}
2021-01-26 15:11:50 +01:00
#[derive(PartialEq, Eq, Hash, Debug)]
2021-10-20 13:47:32 +02:00
#[repr(transparent)]
2020-10-28 19:22:11 +01:00
/// 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```
2021-10-20 13:47:32 +02:00
/// but ```(bffh.perm) > (unrelated.but.more.specific.perm) == false```.
/// This allows to check if PermRule a grants Perm b by checking `a > b`.
2021-10-20 14:11:56 +02:00
pub struct Permission(str);
2020-10-28 19:22:11 +01:00
impl Permission {
2021-10-20 14:11:56 +02:00
#[inline(always)]
// We can't make this `const` just yet because `str` is always a fat pointer meaning we can't
// just const cast it, and `CoerceUnsized` and friends are currently unstable.
2020-11-24 15:57:23 +01:00
pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Permission {
2021-10-20 13:47:32 +02:00
// Safe because s is a valid reference
2020-10-28 23:24:02 +01:00
unsafe { &*(s.as_ref() as *const str as *const Permission) }
}
2021-10-20 14:11:56 +02:00
#[inline(always)]
2020-10-28 19:22:11 +01:00
pub fn as_str(&self) -> &str {
2021-10-20 14:11:56 +02:00
&self.0
2020-10-28 19:22:11 +01:00
}
2021-10-20 14:11:56 +02:00
#[inline(always)]
2020-10-28 23:24:02 +01:00
pub fn iter(&self) -> std::str::Split<char> {
2021-10-20 14:11:56 +02:00
self.0.split('.')
2020-10-28 19:22:11 +01:00
}
}
impl PartialOrd for Permission {
fn partial_cmp(&self, other: &Permission) -> Option<Ordering> {
2020-10-28 23:24:02 +01:00
let mut i = self.iter();
let mut j = other.iter();
let (mut l, mut r);
2020-10-28 19:22:11 +01:00
while {
2020-10-28 23:24:02 +01:00
l = i.next();
r = j.next();
2020-10-28 19:22:11 +01:00
l.is_some() && r.is_some()
} {
if l.unwrap() != r.unwrap() {
return None;
}
}
match (l,r) {
(None, None) => Some(Ordering::Equal),
2020-10-28 23:24:02 +01:00
(Some(_), None) => Some(Ordering::Less),
2020-10-28 19:22:11 +01:00
(None, Some(_)) => Some(Ordering::Greater),
2021-10-20 13:47:32 +02:00
(Some(_), Some(_)) => unreachable!("Broken contract in Permission::partial_cmp: sides \
should never be both Some!"),
2020-10-28 19:22:11 +01:00
}
}
}
impl AsRef<Permission> for Permission {
#[inline]
fn as_ref(&self) -> &Permission {
self
}
}
2020-10-28 19:22:11 +01:00
2021-10-20 13:47:32 +02:00
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(try_from = "String")]
#[serde(into = "String")]
2020-10-28 19:22:11 +01:00
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),
2021-10-20 13:47:32 +02:00
// 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
2020-10-28 19:22:11 +01:00
// 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
2021-09-19 19:47:29 +02:00
pub fn match_perm<P: AsRef<Permission> + ?Sized>(&self, perm: &P) -> bool {
2020-11-17 13:40:44 +01:00
match self {
PermRule::Base(ref base) => base.as_permission() == perm.as_ref(),
PermRule::Children(ref parent) => parent.as_permission() > perm.as_ref() ,
PermRule::Subtree(ref parent) => parent.as_permission() >= perm.as_ref(),
2020-10-28 19:22:11 +01:00
}
}
}
impl fmt::Display for PermRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2020-10-28 23:24:02 +01:00
match self {
2020-10-28 19:22:11 +01:00
PermRule::Base(perm)
=> write!(f, "{}", perm),
PermRule::Children(parent)
=> write!(f,"{}.+", parent),
PermRule::Subtree(parent)
=> write!(f,"{}.*", parent),
}
}
}
impl Into<String> for PermRule {
fn into(self) -> String {
match self {
PermRule::Base(perm) => perm.into_string(),
PermRule::Children(mut perm) => {
perm.push(Permission::new("+"));
perm.into_string()
},
PermRule::Subtree(mut perm) => {
perm.push(Permission::new("+"));
perm.into_string()
}
}
}
}
impl TryFrom<String> for PermRule {
type Error = &'static str;
fn try_from(mut input: String) -> std::result::Result<Self, Self::Error> {
// Check out specifically the last two chars
let len = input.len();
if len <= 2 {
Err("Input string for PermRule is too short")
} else {
match &input[len-2..len] {
".+" => {
input.truncate(len-2);
2021-10-20 13:47:32 +02:00
Ok(PermRule::Children(PermissionBuf::from_string_unchecked(input)))
},
".*" => {
input.truncate(len-2);
2021-10-20 13:47:32 +02:00
Ok(PermRule::Subtree(PermissionBuf::from_string_unchecked(input)))
},
2021-10-20 13:47:32 +02:00
_ => Ok(PermRule::Base(PermissionBuf::from_string_unchecked(input))),
}
}
}
}
2021-01-27 15:40:33 +01:00
#[cfg(test)]
2020-10-28 19:22:11 +01:00
mod tests {
use super::*;
#[test]
fn permission_ord_test() {
2021-10-20 13:47:32 +02:00
assert!(PermissionBuf::from_string_unchecked("bffh.perm".to_string())
> PermissionBuf::from_string_unchecked("bffh.perm.sub".to_string()));
2020-10-28 19:22:11 +01:00
}
2020-11-17 13:40:44 +01:00
#[test]
fn permission_simple_check_test() {
2021-10-20 13:47:32 +02:00
let perm = PermissionBuf::from_string_unchecked("test.perm".to_string());
2020-11-17 13:40:44 +01:00
let rule = PermRule::Base(perm.clone());
assert!(rule.match_perm(&perm));
}
#[test]
fn permission_children_checks_only_children() {
2021-10-20 13:47:32 +02:00
let perm = PermissionBuf::from_string_unchecked("test.perm".to_string());
2020-11-17 13:40:44 +01:00
let rule = PermRule::Children(perm.clone());
2021-10-20 13:47:32 +02:00
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());
2020-11-17 13:40:44 +01:00
assert!(rule.match_perm(&perm));
2021-10-20 13:47:32 +02:00
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));
2020-11-17 13:40:44 +01:00
}
2021-01-27 15:40:33 +01:00
#[test]
fn format_and_read_compatible() {
use std::convert::TryInto;
let testdata = vec![
("testrole", "testsource"),
("", "norole"),
("nosource", "")
].into_iter().map(|(n,s)| (n.to_string(), s.to_string()));
for (name, source) in testdata {
let role = RoleIdentifier { name, source };
let fmt_string = format!("{}", &role);
println!("{:?} is formatted: {}", &role, &fmt_string);
let parsed: RoleIdentifier = fmt_string.try_into().unwrap();
println!("Which parses into {:?}", &parsed);
assert_eq!(role, parsed);
}
}
#[test]
fn rules_from_string_test() {
assert_eq!(
2021-10-20 13:47:32 +02:00
PermRule::Base(PermissionBuf::from_string_unchecked("bffh.perm".to_string())),
PermRule::try_from("bffh.perm".to_string()).unwrap()
);
assert_eq!(
2021-10-20 13:47:32 +02:00
PermRule::Children(PermissionBuf::from_string_unchecked("bffh.perm".to_string())),
PermRule::try_from("bffh.perm.+".to_string()).unwrap()
);
assert_eq!(
2021-10-20 13:47:32 +02:00
PermRule::Subtree(PermissionBuf::from_string_unchecked("bffh.perm".to_string())),
PermRule::try_from("bffh.perm.*".to_string()).unwrap()
);
}
#[test]
fn rules_from_string_edgecases_test() {
assert!(PermRule::try_from("*".to_string()).is_err());
assert!(PermRule::try_from("+".to_string()).is_err());
}
2020-10-28 19:22:11 +01:00
}