2020-02-14 12:20:17 +01:00
|
|
|
|
//! Access control logic
|
|
|
|
|
//!
|
|
|
|
|
|
2020-10-26 12:58:55 +01:00
|
|
|
|
use std::fmt;
|
2020-11-20 13:06:55 +01:00
|
|
|
|
use std::collections::HashMap;
|
2020-10-28 23:24:02 +01:00
|
|
|
|
use std::cmp::Ordering;
|
2021-01-26 15:33:50 +01:00
|
|
|
|
use std::path::Path;
|
2020-09-11 09:57:03 +02:00
|
|
|
|
use std::fs;
|
2020-11-19 14:53:14 +01:00
|
|
|
|
use std::iter::FromIterator;
|
|
|
|
|
use std::convert::{TryFrom, Into};
|
2020-09-11 09:57:03 +02:00
|
|
|
|
|
2021-01-26 15:33:50 +01:00
|
|
|
|
use serde::{Serialize, Deserialize};
|
2020-11-19 14:53:14 +01:00
|
|
|
|
|
2020-09-10 11:50:19 +02:00
|
|
|
|
use crate::error::Result;
|
2020-02-18 16:55:19 +01:00
|
|
|
|
|
2020-11-17 12:26:35 +01:00
|
|
|
|
pub mod internal;
|
2020-10-26 12:58:55 +01:00
|
|
|
|
|
2020-11-24 15:57:23 +01:00
|
|
|
|
use crate::db::user::UserData;
|
2020-12-16 14:04:50 +01:00
|
|
|
|
pub use internal::{init, Internal};
|
2020-10-26 12:58:55 +01:00
|
|
|
|
|
2020-11-20 13:06:55 +01:00
|
|
|
|
pub struct AccessControl {
|
2020-12-16 14:04:50 +01:00
|
|
|
|
pub internal: Internal,
|
2020-11-20 13:06:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-18 17:01:35 +02:00
|
|
|
|
pub const ADMINPERM: &'static str = "bffh.admin";
|
|
|
|
|
pub fn admin_perm() -> &'static Permission {
|
|
|
|
|
Permission::new(ADMINPERM)
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-20 13:06:55 +01:00
|
|
|
|
impl AccessControl {
|
2020-12-16 14:04:50 +01:00
|
|
|
|
pub fn new(internal: Internal) -> Self {
|
2020-11-20 13:06:55 +01:00
|
|
|
|
Self {
|
2021-09-18 17:01:35 +02:00
|
|
|
|
internal,
|
2020-11-20 13:06:55 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-24 15:57:23 +01:00
|
|
|
|
pub async fn check<P: AsRef<Permission>>(&self, user: &UserData, perm: &P) -> Result<bool> {
|
2020-12-16 14:04:50 +01:00
|
|
|
|
if self.internal.check(user, perm.as_ref())? {
|
|
|
|
|
return Ok(true);
|
|
|
|
|
}
|
2020-11-20 13:06:55 +01:00
|
|
|
|
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
2020-11-24 14:16:22 +01:00
|
|
|
|
|
|
|
|
|
pub async fn check_roles<P: AsRef<Permission>>(&self, roles: &[RoleIdentifier], perm: &P)
|
|
|
|
|
-> Result<bool>
|
|
|
|
|
{
|
2021-01-20 13:36:02 +01:00
|
|
|
|
if self.internal.check_roles(roles, perm.as_ref())? {
|
|
|
|
|
return Ok(true);
|
2020-11-24 14:16:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
2021-01-26 15:11:50 +01:00
|
|
|
|
|
2021-09-18 17:01:35 +02:00
|
|
|
|
pub fn collect_permrules(&self, user: &UserData) -> Result<Vec<PermRule>> {
|
|
|
|
|
self.internal.collect_permrules(user)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-26 15:11:50 +01:00
|
|
|
|
pub fn dump_roles(&self) -> Result<Vec<(RoleIdentifier, Role)>> {
|
|
|
|
|
self.internal.dump_roles()
|
|
|
|
|
}
|
2021-01-27 15:40:33 +01:00
|
|
|
|
|
|
|
|
|
pub fn get_role(&self, role_id: &RoleIdentifier) -> Result<Option<Role>> {
|
|
|
|
|
self.internal.get_role(role_id)
|
|
|
|
|
}
|
2020-11-24 14:16:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Debug for AccessControl {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2020-11-24 15:57:23 +01:00
|
|
|
|
let mut b = f.debug_struct("AccessControl");
|
2021-01-20 13:36:02 +01:00
|
|
|
|
b.field("internal", &self.internal.get_type_name().to_string());
|
2020-11-24 14:16:22 +01:00
|
|
|
|
b.finish()
|
|
|
|
|
}
|
2020-11-20 13:06:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 19:22:11 +01:00
|
|
|
|
pub trait RoleDB {
|
2020-11-24 14:16:22 +01:00
|
|
|
|
fn get_type_name(&self) -> &'static str;
|
|
|
|
|
|
2021-01-26 15:33:50 +01:00
|
|
|
|
fn get_role(&self, role_id: &RoleIdentifier) -> Result<Option<Role>>;
|
2020-10-28 19:22:11 +01:00
|
|
|
|
|
|
|
|
|
/// Check if a given user has the given permission
|
|
|
|
|
///
|
|
|
|
|
/// Default implementation which adapter may overwrite with more efficient specialized
|
|
|
|
|
/// implementations.
|
2020-11-24 15:57:23 +01:00
|
|
|
|
fn check(&self, user: &UserData, perm: &Permission) -> Result<bool> {
|
2020-11-17 13:40:44 +01:00
|
|
|
|
self.check_roles(&user.roles, perm)
|
2020-10-28 19:22:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a given permission is granted by any of the given roles or their respective
|
|
|
|
|
/// parents
|
2020-11-10 13:34:09 +01:00
|
|
|
|
///
|
|
|
|
|
/// A Default implementation exists which adapter may overwrite with more efficient specialized
|
2020-10-28 19:22:11 +01:00
|
|
|
|
/// implementations.
|
2020-11-20 13:06:55 +01:00
|
|
|
|
fn check_roles(&self, roles: &[RoleIdentifier], perm: &Permission) -> Result<bool> {
|
2020-10-28 19:22:11 +01:00
|
|
|
|
// Tally all roles. Makes dependent roles easier
|
2021-01-26 15:11:50 +01:00
|
|
|
|
let mut roleset = HashMap::new();
|
2021-01-26 15:33:50 +01:00
|
|
|
|
for role_id in roles {
|
|
|
|
|
self.tally_role(&mut roleset, role_id)?;
|
2020-10-28 19:22:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iter all unique role->permissions we've found and early return on match.
|
2021-01-26 15:11:50 +01:00
|
|
|
|
for (_roleid, role) in roleset.iter() {
|
2020-11-17 13:40:44 +01:00
|
|
|
|
for perm_rule in role.permissions.iter() {
|
2020-11-20 13:06:55 +01:00
|
|
|
|
if perm_rule.match_perm(&perm) {
|
2020-10-28 19:22:11 +01:00
|
|
|
|
return Ok(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tally a role dependency tree into a set
|
|
|
|
|
///
|
2020-11-10 13:34:09 +01:00
|
|
|
|
/// A Default implementation exists which adapter may overwrite with more efficient
|
|
|
|
|
/// implementations.
|
2021-01-26 15:33:50 +01:00
|
|
|
|
fn tally_role(&self, roles: &mut HashMap<RoleIdentifier, Role>, role_id: &RoleIdentifier) -> Result<()> {
|
|
|
|
|
if let Some(role) = self.get_role(role_id)? {
|
2020-10-28 19:22:11 +01:00
|
|
|
|
// Only check and tally parents of a role at the role itself if it's the first time we
|
|
|
|
|
// see it
|
2021-01-26 15:33:50 +01:00
|
|
|
|
if !roles.contains_key(&role_id) {
|
2020-10-28 19:22:11 +01:00
|
|
|
|
for parent in role.parents.iter() {
|
2020-10-28 23:24:02 +01:00
|
|
|
|
self.tally_role(roles, parent)?;
|
2020-10-28 19:22:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-26 15:33:50 +01:00
|
|
|
|
roles.insert(role_id.clone(), role);
|
2020-10-28 19:22:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2021-09-18 17:01:35 +02:00
|
|
|
|
|
|
|
|
|
fn collect_permrules(&self, user: &UserData) -> Result<Vec<PermRule>> {
|
|
|
|
|
let mut roleset = HashMap::new();
|
|
|
|
|
for role_id in user.roles.iter() {
|
|
|
|
|
self.tally_role(&mut roleset, role_id)?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut output = Vec::new();
|
|
|
|
|
|
|
|
|
|
// Iter all unique role->permissions we've found and early return on match.
|
|
|
|
|
for (_roleid, role) in roleset.iter() {
|
|
|
|
|
output.extend(role.permissions.iter().cloned())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(output);
|
|
|
|
|
}
|
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.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
2020-10-26 12:58:55 +01:00
|
|
|
|
pub struct Role {
|
2020-11-19 14:53:14 +01:00
|
|
|
|
// 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>,
|
2020-11-19 14:53:14 +01:00
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 14:53:14 +01:00
|
|
|
|
impl Role {
|
|
|
|
|
fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<RoleIdentifier, Role>> {
|
|
|
|
|
let content = fs::read(path)?;
|
|
|
|
|
let file_roles: HashMap<String, Role> = toml::from_slice(&content[..])?;
|
|
|
|
|
|
|
|
|
|
Ok(HashMap::from_iter(file_roles.into_iter().map(|(key, value)| {
|
|
|
|
|
(RoleIdentifier::local_from_str("lmdb".to_string(), key), value)
|
|
|
|
|
})))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
2020-11-19 14:53:14 +01:00
|
|
|
|
#[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
|
|
|
|
}
|
2020-11-19 14:53:14 +01:00
|
|
|
|
|
|
|
|
|
impl fmt::Display for RoleIdentifier {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2021-01-27 15:40:33 +01:00
|
|
|
|
write!(f, "{}/{}", self.name, self.source)
|
2020-11-19 14:53:14 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
Err(RoleFromStrError::Invalid)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-19 14:53:14 +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
|
|
|
|
|
2020-11-19 14:53:14 +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-11-19 14:53:14 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 14:53:14 +01:00
|
|
|
|
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
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
2020-10-28 19:22:11 +01:00
|
|
|
|
/// 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 {
|
2020-10-28 23:24:02 +01:00
|
|
|
|
match self {
|
2020-10-28 19:22:11 +01:00
|
|
|
|
PermIdentifier::Local(perm)
|
|
|
|
|
=> write!(f, "{}", perm),
|
|
|
|
|
PermIdentifier::Remote(perm, source)
|
|
|
|
|
=> write!(f, "{}@{}", perm, source),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
|
fn is_sep_char(c: char) -> bool {
|
|
|
|
|
c == '.'
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-17 13:40:44 +01:00
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, 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
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
2020-10-28 19:22:11 +01:00
|
|
|
|
#[repr(transparent)]
|
2020-11-19 14:53:14 +01:00
|
|
|
|
#[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 {
|
|
|
|
|
/// Allocate an empty `PermissionBuf`
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
PermissionBuf { inner: String::new() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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-09-18 17:01:35 +02:00
|
|
|
|
pub const fn from_string(inner: String) -> Self {
|
2020-10-28 19:22:11 +01:00
|
|
|
|
Self { inner }
|
|
|
|
|
}
|
2020-11-19 14:53:14 +01:00
|
|
|
|
|
2020-11-24 14:16:22 +01:00
|
|
|
|
pub fn from_perm(perm: &Permission) -> Self {
|
|
|
|
|
Self { inner: perm.inner.to_string() }
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 14:53:14 +01:00
|
|
|
|
pub fn into_string(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 {
|
|
|
|
|
&self.inner[..]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[repr(transparent)]
|
2021-01-26 15:11:50 +01:00
|
|
|
|
#[derive(PartialEq, Eq, Hash, Debug)]
|
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```
|
|
|
|
|
/// but ```(bffh.perm) > (unrelated.but.specific.perm) == false```
|
|
|
|
|
pub struct Permission {
|
|
|
|
|
inner: str
|
|
|
|
|
}
|
|
|
|
|
impl Permission {
|
2020-11-24 15:57:23 +01:00
|
|
|
|
pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Permission {
|
2020-10-28 23:24:02 +01:00
|
|
|
|
unsafe { &*(s.as_ref() as *const str as *const Permission) }
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 19:22:11 +01:00
|
|
|
|
pub fn as_str(&self) -> &str {
|
2020-10-28 23:24:02 +01:00
|
|
|
|
&self.inner
|
2020-10-28 19:22:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
|
pub fn iter(&self) -> std::str::Split<char> {
|
2020-10-28 19:22:11 +01:00
|
|
|
|
self.inner.split('.')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2021-01-26 15:47:58 +01:00
|
|
|
|
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),
|
|
|
|
|
(Some(_), Some(_)) => panic!("Broken contract in Permission::partial_cmp: sides should never be both Some!"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 14:53:14 +01:00
|
|
|
|
impl AsRef<Permission> for Permission {
|
|
|
|
|
#[inline]
|
|
|
|
|
fn as_ref(&self) -> &Permission {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-28 19:22:11 +01:00
|
|
|
|
|
2020-10-28 23:24:02 +01:00
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
2020-11-19 14:53:14 +01:00
|
|
|
|
#[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),
|
|
|
|
|
// 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
|
2021-09-18 17:01:35 +02:00
|
|
|
|
pub fn match_perm<P: AsRef<Permission>>(&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),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 14:53:14 +01:00
|
|
|
|
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);
|
|
|
|
|
Ok(PermRule::Children(PermissionBuf::from_string(input)))
|
|
|
|
|
},
|
|
|
|
|
".*" => {
|
|
|
|
|
input.truncate(len-2);
|
|
|
|
|
Ok(PermRule::Subtree(PermissionBuf::from_string(input)))
|
|
|
|
|
},
|
|
|
|
|
_ => Ok(PermRule::Base(PermissionBuf::from_string(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() {
|
2020-10-28 23:25:59 +01:00
|
|
|
|
assert!(PermissionBuf::from_string("bffh.perm".to_string())
|
|
|
|
|
> PermissionBuf::from_string("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() {
|
|
|
|
|
let perm = PermissionBuf::from_string("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 rule = PermRule::Children(perm.clone());
|
|
|
|
|
|
|
|
|
|
assert!(rule.match_perm(&perm));
|
|
|
|
|
}
|
2020-11-19 14:53:14 +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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 14:53:14 +01:00
|
|
|
|
#[test]
|
|
|
|
|
fn load_examples_roles_test() {
|
2020-11-19 15:10:42 +01:00
|
|
|
|
let mut roles = Role::load_file("examples/roles.toml")
|
2020-11-19 14:53:14 +01:00
|
|
|
|
.expect("Couldn't load the example role defs. Does `examples/roles.toml` exist?");
|
|
|
|
|
|
2020-11-19 15:10:42 +01:00
|
|
|
|
let expected = vec![
|
2021-09-10 21:19:30 +02:00
|
|
|
|
(RoleIdentifier { name: "anotherrole".to_string(), source: "lmdb".to_string() },
|
|
|
|
|
Role {
|
|
|
|
|
parents: vec![],
|
|
|
|
|
permissions: vec![],
|
|
|
|
|
}),
|
2021-01-27 15:40:33 +01:00
|
|
|
|
(RoleIdentifier { name: "testrole".to_string(), source: "lmdb".to_string() },
|
2020-11-19 15:10:42 +01:00
|
|
|
|
Role {
|
|
|
|
|
parents: vec![],
|
|
|
|
|
permissions: vec![
|
|
|
|
|
PermRule::Subtree(PermissionBuf::from_string("lab.test".to_string()))
|
|
|
|
|
],
|
|
|
|
|
}),
|
2021-01-27 15:40:33 +01:00
|
|
|
|
(RoleIdentifier { name: "somerole".to_string(), source: "lmdb".to_string() },
|
2020-11-19 15:10:42 +01:00
|
|
|
|
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()))
|
|
|
|
|
],
|
|
|
|
|
}),
|
2021-01-27 15:40:33 +01:00
|
|
|
|
(RoleIdentifier { name: "testparent".to_string(), source: "lmdb".to_string() },
|
2020-11-19 15:10:42 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
2020-11-19 14:53:14 +01:00
|
|
|
|
|
2020-11-19 15:10:42 +01:00
|
|
|
|
assert!(roles.is_empty())
|
2020-11-19 14:53:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn rules_from_string_test() {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
PermRule::Base(PermissionBuf::from_string("bffh.perm".to_string())),
|
|
|
|
|
PermRule::try_from("bffh.perm".to_string()).unwrap()
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
PermRule::Children(PermissionBuf::from_string("bffh.perm".to_string())),
|
|
|
|
|
PermRule::try_from("bffh.perm.+".to_string()).unwrap()
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
PermRule::Subtree(PermissionBuf::from_string("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
|
|
|
|
}
|