mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-11-22 23:07:56 +01:00
Adds permission type
This commit is contained in:
parent
cd609df85f
commit
d304ff40d4
236
src/db/access.rs
236
src/db/access.rs
@ -25,13 +25,60 @@ mod adapter_lmdb;
|
|||||||
use adapter_lmdb::PermissionsDB;
|
use adapter_lmdb::PermissionsDB;
|
||||||
pub use adapter_lmdb::init;
|
pub use adapter_lmdb::init;
|
||||||
|
|
||||||
// FIXME: fabinfra/fabaccess/bffh#3
|
pub trait RoleDB {
|
||||||
pub type RoleIdentifier = u64;
|
|
||||||
pub type PermIdentifier = u64;
|
|
||||||
|
|
||||||
pub trait AccessDB {
|
|
||||||
fn check(&self, userID: UserIdentifier, permID: PermIdentifier) -> Result<bool>;
|
|
||||||
fn get_role(&self, roleID: RoleIdentifier) -> Result<Option<Role>>;
|
fn get_role(&self, roleID: RoleIdentifier) -> Result<Option<Role>>;
|
||||||
|
|
||||||
|
/// 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<bool> {
|
||||||
|
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<bool> {
|
||||||
|
// 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<Role>, 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
|
/// A "Role" from the Authorization perspective
|
||||||
@ -62,7 +109,7 @@ pub struct Role {
|
|||||||
type SourceID = String;
|
type SourceID = String;
|
||||||
|
|
||||||
/// Universal (relative) id of a role
|
/// Universal (relative) id of a role
|
||||||
enum RoleID {
|
enum RoleIdentifier {
|
||||||
/// The role comes from this instance
|
/// The role comes from this instance
|
||||||
Local {
|
Local {
|
||||||
/// Locally unique name for the role. No other role at this instance no matter the source
|
/// 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 {
|
impl fmt::Display for RoleID {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
RoleID::Local {name, source} => write!(f, "{}/{}@local", name, source),
|
RoleIdentifier::Local {name, source} => write!(f, "{}/{}@local", name, source),
|
||||||
RoleID::Remote {name, location} => write!(f, "{}@{}", name, location),
|
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<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('.')
|
||||||
|
}
|
||||||
|
self.inner.push(perm.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_string(inner: String) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<Permission> 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<Char> {
|
||||||
|
self.inner.split('.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Permission {
|
||||||
|
fn partial_cmp(&self, other: &Permission) -> Option<Ordering> {
|
||||||
|
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<P: AsRef<Permission>>(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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@ use lmdb::{Environment, Transaction, RwTransaction, Cursor};
|
|||||||
use crate::config::Settings;
|
use crate::config::Settings;
|
||||||
use crate::error::Result;
|
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};
|
use crate::db::user::{UserIdentifier, User};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -34,7 +34,7 @@ impl PermissionsDB {
|
|||||||
|
|
||||||
/// Check if a given user has the given permission
|
/// Check if a given user has the given permission
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn check<T: Transaction>(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result<bool> {
|
pub fn _check<T: Transaction>(&self, txn: &T, userID: UserIdentifier, permID: PermIdentifier) -> Result<bool> {
|
||||||
if let Some(user) = self.get_user(txn, userID)? {
|
if let Some(user) = self.get_user(txn, userID)? {
|
||||||
// Tally all roles. Makes dependent roles easier
|
// Tally all roles. Makes dependent roles easier
|
||||||
let mut roles = HashSet::new();
|
let mut roles = HashSet::new();
|
||||||
@ -72,7 +72,7 @@ impl PermissionsDB {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: RoleIdentifier) -> Result<Option<Role>> {
|
pub fn _get_role<'txn, T: Transaction>(&self, txn: &'txn T, roleID: RoleIdentifier) -> Result<Option<Role>> {
|
||||||
match txn.get(self.roledb, &roleID.to_ne_bytes()) {
|
match txn.get(self.roledb, &roleID.to_ne_bytes()) {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
Ok(Some(flexbuffers::from_slice(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<bool> {
|
fn check(&self, userID: UserIdentifier, permID: PermIdentifier) -> Result<bool> {
|
||||||
let txn = self.env.begin_ro_txn()?;
|
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<Option<Role>> {
|
fn get_role(&self, roleID: RoleIdentifier) -> Result<Option<Role>> {
|
||||||
let txn = self.env.begin_ro_txn()?;
|
let txn = self.env.begin_ro_txn()?;
|
||||||
self.inner.get_role(&txn, roleID)
|
self._get_role(&txn, roleID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ use std::collections::HashMap;
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
/// The identification of this user.
|
/// The identification of this user.
|
||||||
#[serde(skip, default = get_uid)]
|
|
||||||
pub id: UserIdentifier,
|
pub id: UserIdentifier,
|
||||||
|
|
||||||
/// A Person has N ≥ 0 roles.
|
/// A Person has N ≥ 0 roles.
|
||||||
@ -40,10 +39,6 @@ impl UserIdentifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_uid() -> UserIdentifier {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for UserIdentifier {
|
impl fmt::Display for UserIdentifier {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let r = write!(f, "{}", self.uid);
|
let r = write!(f, "{}", self.uid);
|
||||||
|
@ -2,6 +2,7 @@ use futures_signals::signal::Signal;
|
|||||||
|
|
||||||
use crate::machine;
|
use crate::machine;
|
||||||
use crate::access;
|
use crate::access;
|
||||||
|
use crate::db::user::UserIdentifier;
|
||||||
|
|
||||||
struct Network {
|
struct Network {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user