Roles dumping

This commit is contained in:
Gregor Reitzenstein 2021-01-26 14:11:50 +00:00
parent a77841edce
commit fbc661f478
4 changed files with 94 additions and 67 deletions

View File

@ -60,6 +60,10 @@ impl AccessControl {
return Ok(false);
}
pub fn dump_roles(&self) -> Result<Vec<(RoleIdentifier, Role)>> {
self.internal.dump_roles()
}
}
impl fmt::Debug for AccessControl {
@ -90,13 +94,13 @@ pub trait RoleDB {
/// implementations.
fn check_roles(&self, roles: &[RoleIdentifier], perm: &Permission) -> Result<bool> {
// Tally all roles. Makes dependent roles easier
let mut roleset = HashSet::new();
let mut roleset = HashMap::new();
for roleID in roles {
self.tally_role(&mut roleset, roleID)?;
}
// Iter all unique role->permissions we've found and early return on match.
for role in roleset.iter() {
for (_roleid, role) in roleset.iter() {
for perm_rule in role.permissions.iter() {
if perm_rule.match_perm(&perm) {
return Ok(true);
@ -111,16 +115,16 @@ pub trait RoleDB {
///
/// A Default implementation exists which adapter may overwrite with more efficient
/// implementations.
fn tally_role(&self, roles: &mut HashSet<Role>, roleID: &RoleIdentifier) -> Result<()> {
fn tally_role(&self, roles: &mut HashMap<RoleIdentifier, Role>, roleID: &RoleIdentifier) -> Result<()> {
if let Some(role) = self.get_role(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) {
if !roles.contains_key(&roleID) {
for parent in role.parents.iter() {
self.tally_role(roles, parent)?;
}
roles.insert(role);
roles.insert(roleID.clone(), role);
}
}
@ -371,7 +375,7 @@ impl fmt::Display for PermissionBuf {
}
#[repr(transparent)]
#[derive(PartialEq, Eq, Hash)]
#[derive(PartialEq, Eq, Hash, Debug)]
/// A borrowed permission string
///
/// Permissions have total equality and partial ordering.

View File

@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::collections::HashMap;
use std::convert::TryInto;
@ -11,7 +11,7 @@ use flexbuffers;
use serde::{Serialize, Deserialize};
use slog::Logger;
use lmdb::{Environment, Transaction, RwTransaction, Cursor};
use lmdb::{Environment, Transaction, RwTransaction, Cursor, Iter};
use crate::config::Settings;
use crate::error::Result;
@ -36,36 +36,45 @@ impl Internal {
pub fn _check<T: Transaction, P: AsRef<Permission>>(&self, txn: &T, user: &UserData, perm: &P)
-> Result<bool>
{
debug!(self.log, "Checking user {:?} for permission {:?}", user, perm.as_ref());
// Tally all roles. Makes dependent roles easier
let mut roles = HashSet::new();
let mut roles = HashMap::new();
for roleID in user.roles.iter() {
debug!(self.log, "Tallying role {} for its parents", roleID);
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 (roleid, role) in roles.iter() {
debug!(self.log, " checking role {}", roleid);
for perm_rule in role.permissions.iter() {
if perm_rule.match_perm(perm) {
debug!(self.log, " matches permission rule {}", perm_rule);
return Ok(true);
}
trace!(self.log, " rejecting permission rule {}", perm_rule);
}
}
debug!(self.log, "Checked all roles, rejecting access");
return Ok(false);
}
fn _tally_role<T: Transaction>(&self, txn: &T, roles: &mut HashSet<Role>, roleID: &RoleIdentifier) -> Result<()> {
fn _tally_role<T: Transaction>(&self, txn: &T, roles: &mut HashMap<RoleIdentifier, 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) {
if !roles.contains_key(&roleID) {
for parent in role.parents.iter() {
self._tally_role(txn, roles, parent)?;
}
roles.insert(role);
roles.insert(roleID.clone(), role);
}
} else {
info!(self.log, "Did not find role {} while trying to tally", roleID);
}
Ok(())
@ -82,55 +91,57 @@ impl Internal {
}
}
fn put_role(&self, txn: &mut RwTransaction, roleID: &RoleIdentifier, role: Role) -> Result<()> {
let bytes = flexbuffers::to_vec(role)?;
let string = format!("{}", roleID);
txn.put(self.roledb, &string.as_bytes(), &bytes, lmdb::WriteFlags::empty())?;
fn put_role(&self, txn: &mut RwTransaction, roleID: &RoleIdentifier, role: Role) -> Result<()> {
let bytes = flexbuffers::to_vec(role)?;
let string = format!("{}", roleID);
txn.put(self.roledb, &string.as_bytes(), &bytes, lmdb::WriteFlags::empty())?;
Ok(())
}
Ok(())
}
pub fn dump_db<T: Transaction>(&mut self, txn: &T, mut path: PathBuf) -> Result<()> {
path.push("roles");
let mut k = Ok(());
if !path.is_dir() {
k = fs::create_dir(&path);
}
if let Err(e) = k {
error!(self.log, "Failed to create 'roles' directory: {}, skipping!", e);
return Ok(())
} else {
// Rust's stdlib considers the last element the file name even when it's a directory so
// we have to put a dummy here for .set_filename() to work correctly
path.push("dummy");
self.dump_roles(txn, path.clone())?;
path.pop();
}
path.pop();
Ok(())
}
pub fn dump_roles(&self) -> Result<Vec<(RoleIdentifier, Role)>> {
let txn = self.env.begin_ro_txn()?;
self.dump_roles_txn(&txn)
}
pub fn dump_roles_txn<T: Transaction>(&self, txn: &T) -> Result<Vec<(RoleIdentifier, Role)>> {
let mut cursor = txn.open_ro_cursor(self.roledb)?;
fn dump_roles<T: Transaction>(&mut self, txn: &T, mut path: PathBuf) -> Result<()> {
// TODO implement this for the new format
unimplemented!()
}
let mut vec = Vec::new();
for r in cursor.iter_start() {
match r {
Ok( (k,v) ) => {
let role_id_str = unsafe { std::str::from_utf8_unchecked(k) };
let role_id = role_id_str.parse::<RoleIdentifier>().unwrap();
let role = flexbuffers::from_slice(v)?;
vec.push((role_id, role));
},
Err(e) => return Err(e.into()),
}
}
pub fn load_roles<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut txn = self.env.begin_rw_txn()?;
self.load_roles_txn(&mut txn, path.as_ref())
}
fn load_roles_txn(&self, txn: &mut RwTransaction, path: &Path) -> Result<()> {
let roles = Role::load_file(path)?;
Ok(vec)
}
for (k,v) in roles.iter() {
self.put_role(txn, k, v.clone())?;
}
pub fn load_roles<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut txn = self.env.begin_rw_txn()?;
self.load_roles_txn(&mut txn, path.as_ref())?;
debug!(self.log, "Loaded roles: {:?}", roles);
// In case the above didn't error, commit.
txn.commit();
Ok(())
}
fn load_roles_txn(&self, txn: &mut RwTransaction, path: &Path) -> Result<()> {
let roles = Role::load_file(path)?;
Ok(())
}
for (k,v) in roles.iter() {
self.put_role(txn, k, v.clone())?;
}
debug!(self.log, "Loaded roles: {:?}", roles);
Ok(())
}
}
impl RoleDB for Internal {
@ -148,7 +159,7 @@ impl RoleDB for Internal {
self._get_role(&txn, roleID)
}
fn tally_role(&self, roles: &mut HashSet<Role>, roleID: &RoleIdentifier) -> Result<()> {
fn tally_role(&self, roles: &mut HashMap<RoleIdentifier, Role>, roleID: &RoleIdentifier) -> Result<()> {
let txn = self.env.begin_ro_txn()?;
self._tally_role(&txn, roles, roleID)
}

View File

@ -31,6 +31,7 @@ pub trait Sensor {
type BoxSensor = Box<dyn Sensor + Send>;
pub struct Initiator {
log: Logger,
signal: MutableSignalCloned<Option<Machine>>,
machine: Option<Machine>,
future: Option<BoxFuture<'static, (Option<User>, MachineState)>>,
@ -41,8 +42,9 @@ pub struct Initiator {
}
impl Initiator {
pub fn new(sensor: BoxSensor, signal: MutableSignalCloned<Option<Machine>>) -> Self {
pub fn new(log: Logger, sensor: BoxSensor, signal: MutableSignalCloned<Option<Machine>>) -> Self {
Self {
log: log,
signal: signal,
machine: None,
future: None,
@ -52,11 +54,11 @@ impl Initiator {
}
}
pub fn wrap(sensor: BoxSensor) -> (Mutable<Option<Machine>>, Self) {
pub fn wrap(log: Logger, sensor: BoxSensor) -> (Mutable<Option<Machine>>, Self) {
let m = Mutable::new(None);
let s = m.signal_cloned();
(m, Self::new(sensor, s))
(m, Self::new(log, sensor, s))
}
}
@ -71,22 +73,30 @@ impl Future for Initiator {
Poll::Pending => { }
Poll::Ready(None) => return Poll::Ready(()),
// Keep in mind this is actually an Option<Machine>
Poll::Ready(Some(machine)) => this.machine = machine,
Poll::Ready(Some(machine)) => {
match machine.as_ref().map(|m| m.try_lock()) {
None => info!(this.log, "Deinstalled machine"),
Some(None) => info!(this.log, "Installed new machine with locked mutex!"),
Some(Some(g)) => info!(this.log, "Installed new machine {}", g.id),
}
this.machine = machine;
},
}
// Do as much work as we can:
loop {
// Always poll the state change future first
if let Some(ref mut f) = this.state_change_fut {
print!("Polling state change fut ...");
match Future::poll(Pin::new(f), cx) {
// If there is a state change future and it would block we return early
Poll::Pending => {
println!(" blocked");
debug!(this.log, "State change blocked");
return Poll::Pending;
},
Poll::Ready(Ok(tok)) => {
println!(" returned ok");
debug!(this.log, "State change returned ok");
// Explicity drop the future
let _ = this.state_change_fut.take();
@ -94,7 +104,7 @@ impl Future for Initiator {
this.token.replace(tok);
}
Poll::Ready(Err(e)) => {
println!(" returned err: {:?}", e);
info!(this.log, "State change returned err: {}", e);
// Explicity drop the future
let _ = this.state_change_fut.take();
}
@ -107,7 +117,7 @@ impl Future for Initiator {
this.future = Some(this.sensor.run_sensor());
},
Some(Poll::Ready((user, state))) => {
println!("New sensor fut");
debug!(this.log, "Sensor returned a new state");
this.future.take();
let f = this.machine.as_mut().map(|machine| {
machine.request_state_change(user.as_ref(), state)
@ -132,7 +142,7 @@ pub fn load(log: &Logger, client: &AsyncClient, config: &Config) -> Result<(Init
let mut v = Vec::new();
for (name, initiator) in initiators {
let (m, i) = Initiator::wrap(initiator);
let (m, i) = Initiator::wrap(log.new(o!("name" => name.clone())), initiator);
map.insert(name.clone(), m);
v.push(i);
}
@ -187,7 +197,7 @@ impl Sensor for Dummy {
} else {
let user = User::new(
UserId::new("test".to_string(), None, None),
UserData::new(vec![], 0),
UserData::new(vec![crate::db::access::RoleIdentifier::local_from_str("lmdb".to_string(), "testrole".to_string())], 0),
);
let id = user.id.clone();
return (Some(user), MachineState::used(Some(id)));

View File

@ -128,7 +128,9 @@ fn maybe(matches: clap::ArgMatches, log: Arc<Logger>) -> Result<(), Error> {
debug!(log, "Loaded Config: {:?}", config);
if matches.is_present("dump") {
error!(log, "Dumping is currently not implemented");
let db = db::Databases::new(&log, &config)?;
let v = db.access.dump_roles();
info!(log, "Roles {:?}", v);
Ok(())
} else if matches.is_present("load") {
let db = db::Databases::new(&log, &config)?;