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); return Ok(false);
} }
pub fn dump_roles(&self) -> Result<Vec<(RoleIdentifier, Role)>> {
self.internal.dump_roles()
}
} }
impl fmt::Debug for AccessControl { impl fmt::Debug for AccessControl {
@ -90,13 +94,13 @@ pub trait RoleDB {
/// implementations. /// implementations.
fn check_roles(&self, roles: &[RoleIdentifier], perm: &Permission) -> Result<bool> { fn check_roles(&self, roles: &[RoleIdentifier], perm: &Permission) -> Result<bool> {
// Tally all roles. Makes dependent roles easier // Tally all roles. Makes dependent roles easier
let mut roleset = HashSet::new(); let mut roleset = HashMap::new();
for roleID in roles { for roleID in roles {
self.tally_role(&mut roleset, roleID)?; self.tally_role(&mut roleset, roleID)?;
} }
// Iter all unique role->permissions we've found and early return on match. // 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() { for perm_rule in role.permissions.iter() {
if perm_rule.match_perm(&perm) { if perm_rule.match_perm(&perm) {
return Ok(true); return Ok(true);
@ -111,16 +115,16 @@ pub trait RoleDB {
/// ///
/// A Default implementation exists which adapter may overwrite with more efficient /// A Default implementation exists which adapter may overwrite with more efficient
/// implementations. /// 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)? { 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 // Only check and tally parents of a role at the role itself if it's the first time we
// see it // see it
if !roles.contains(&role) { if !roles.contains_key(&roleID) {
for parent in role.parents.iter() { for parent in role.parents.iter() {
self.tally_role(roles, parent)?; self.tally_role(roles, parent)?;
} }
roles.insert(role); roles.insert(roleID.clone(), role);
} }
} }
@ -371,7 +375,7 @@ impl fmt::Display for PermissionBuf {
} }
#[repr(transparent)] #[repr(transparent)]
#[derive(PartialEq, Eq, Hash)] #[derive(PartialEq, Eq, Hash, Debug)]
/// A borrowed permission string /// A borrowed permission string
/// ///
/// Permissions have total equality and partial ordering. /// 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; use std::convert::TryInto;
@ -11,7 +11,7 @@ use flexbuffers;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use slog::Logger; use slog::Logger;
use lmdb::{Environment, Transaction, RwTransaction, Cursor}; use lmdb::{Environment, Transaction, RwTransaction, Cursor, Iter};
use crate::config::Settings; use crate::config::Settings;
use crate::error::Result; 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) pub fn _check<T: Transaction, P: AsRef<Permission>>(&self, txn: &T, user: &UserData, perm: &P)
-> Result<bool> -> Result<bool>
{ {
debug!(self.log, "Checking user {:?} for permission {:?}", user, perm.as_ref());
// Tally all roles. Makes dependent roles easier // Tally all roles. Makes dependent roles easier
let mut roles = HashSet::new(); let mut roles = HashMap::new();
for roleID in user.roles.iter() { for roleID in user.roles.iter() {
debug!(self.log, "Tallying role {} for its parents", roleID);
self._tally_role(txn, &mut roles, roleID)?; self._tally_role(txn, &mut roles, roleID)?;
} }
// Iter all unique role->permissions we've found and early return on match. // Iter all unique role->permissions we've found and early return on match.
// TODO: Change this for negative permissions? // 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() { for perm_rule in role.permissions.iter() {
if perm_rule.match_perm(perm) { if perm_rule.match_perm(perm) {
debug!(self.log, " matches permission rule {}", perm_rule);
return Ok(true); return Ok(true);
} }
trace!(self.log, " rejecting permission rule {}", perm_rule);
} }
} }
debug!(self.log, "Checked all roles, rejecting access");
return Ok(false); 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)? { 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 // Only check and tally parents of a role at the role itself if it's the first time we
// see it // see it
if !roles.contains(&role) { if !roles.contains_key(&roleID) {
for parent in role.parents.iter() { for parent in role.parents.iter() {
self._tally_role(txn, roles, parent)?; 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(()) Ok(())
@ -90,35 +99,37 @@ impl Internal {
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)?;
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()),
}
} }
fn dump_roles<T: Transaction>(&mut self, txn: &T, mut path: PathBuf) -> Result<()> { Ok(vec)
// TODO implement this for the new format
unimplemented!()
} }
pub fn load_roles<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn load_roles<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut txn = self.env.begin_rw_txn()?; let mut txn = self.env.begin_rw_txn()?;
self.load_roles_txn(&mut txn, path.as_ref()) self.load_roles_txn(&mut txn, path.as_ref())?;
// In case the above didn't error, commit.
txn.commit();
Ok(())
} }
fn load_roles_txn(&self, txn: &mut RwTransaction, path: &Path) -> Result<()> { fn load_roles_txn(&self, txn: &mut RwTransaction, path: &Path) -> Result<()> {
let roles = Role::load_file(path)?; let roles = Role::load_file(path)?;
@ -148,7 +159,7 @@ impl RoleDB for Internal {
self._get_role(&txn, roleID) 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()?; let txn = self.env.begin_ro_txn()?;
self._tally_role(&txn, roles, roleID) self._tally_role(&txn, roles, roleID)
} }

View File

@ -31,6 +31,7 @@ pub trait Sensor {
type BoxSensor = Box<dyn Sensor + Send>; type BoxSensor = Box<dyn Sensor + Send>;
pub struct Initiator { pub struct Initiator {
log: Logger,
signal: MutableSignalCloned<Option<Machine>>, signal: MutableSignalCloned<Option<Machine>>,
machine: Option<Machine>, machine: Option<Machine>,
future: Option<BoxFuture<'static, (Option<User>, MachineState)>>, future: Option<BoxFuture<'static, (Option<User>, MachineState)>>,
@ -41,8 +42,9 @@ pub struct Initiator {
} }
impl 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 { Self {
log: log,
signal: signal, signal: signal,
machine: None, machine: None,
future: 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 m = Mutable::new(None);
let s = m.signal_cloned(); 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::Pending => { }
Poll::Ready(None) => return Poll::Ready(()), Poll::Ready(None) => return Poll::Ready(()),
// Keep in mind this is actually an Option<Machine> // 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: // Do as much work as we can:
loop { loop {
// Always poll the state change future first // Always poll the state change future first
if let Some(ref mut f) = this.state_change_fut { if let Some(ref mut f) = this.state_change_fut {
print!("Polling state change fut ...");
match Future::poll(Pin::new(f), cx) { match Future::poll(Pin::new(f), cx) {
// If there is a state change future and it would block we return early // If there is a state change future and it would block we return early
Poll::Pending => { Poll::Pending => {
println!(" blocked"); debug!(this.log, "State change blocked");
return Poll::Pending; return Poll::Pending;
}, },
Poll::Ready(Ok(tok)) => { Poll::Ready(Ok(tok)) => {
println!(" returned ok"); debug!(this.log, "State change returned ok");
// Explicity drop the future // Explicity drop the future
let _ = this.state_change_fut.take(); let _ = this.state_change_fut.take();
@ -94,7 +104,7 @@ impl Future for Initiator {
this.token.replace(tok); this.token.replace(tok);
} }
Poll::Ready(Err(e)) => { Poll::Ready(Err(e)) => {
println!(" returned err: {:?}", e); info!(this.log, "State change returned err: {}", e);
// Explicity drop the future // Explicity drop the future
let _ = this.state_change_fut.take(); let _ = this.state_change_fut.take();
} }
@ -107,7 +117,7 @@ impl Future for Initiator {
this.future = Some(this.sensor.run_sensor()); this.future = Some(this.sensor.run_sensor());
}, },
Some(Poll::Ready((user, state))) => { Some(Poll::Ready((user, state))) => {
println!("New sensor fut"); debug!(this.log, "Sensor returned a new state");
this.future.take(); this.future.take();
let f = this.machine.as_mut().map(|machine| { let f = this.machine.as_mut().map(|machine| {
machine.request_state_change(user.as_ref(), state) 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(); let mut v = Vec::new();
for (name, initiator) in initiators { 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); map.insert(name.clone(), m);
v.push(i); v.push(i);
} }
@ -187,7 +197,7 @@ impl Sensor for Dummy {
} else { } else {
let user = User::new( let user = User::new(
UserId::new("test".to_string(), None, None), 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(); let id = user.id.clone();
return (Some(user), MachineState::used(Some(id))); 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); debug!(log, "Loaded Config: {:?}", config);
if matches.is_present("dump") { 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(()) Ok(())
} else if matches.is_present("load") { } else if matches.is_present("load") {
let db = db::Databases::new(&log, &config)?; let db = db::Databases::new(&log, &config)?;