From a09c3d3880ddada6700244e762f8f47a2ab689c3 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Tue, 7 Dec 2021 23:02:26 +0100 Subject: [PATCH] Fix #29 --- examples/bffh.dhall | 10 +-- src/api/machine.rs | 35 +-------- src/api/machines.rs | 2 +- src/config.rs | 2 +- src/db.rs | 3 +- src/db/access.rs | 97 ++++++++++++++++++++++++ src/initiator.rs | 55 ++++++++------ src/machine.rs | 181 +++++++++++++++++++++++++++++++------------- 8 files changed, 269 insertions(+), 116 deletions(-) diff --git a/examples/bffh.dhall b/examples/bffh.dhall index e769991..84c5f70 100644 --- a/examples/bffh.dhall +++ b/examples/bffh.dhall @@ -8,7 +8,7 @@ , { machine = "Yetmore", actor = "FailBash"} ] , actors = - { Shelly_1234 = { module = "Shelly", params = + { Shelly1234 = { module = "Shelly", params = { topic = "Topic1234" }} , Bash = { module = "Process", params = { cmd = "./examples/actor.sh" @@ -22,10 +22,10 @@ { cmd = "./examples/fail-actor.sh" }} } - , init_connections = [] : List { machine : Text, initiator : Text } ---, init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] - , initiators = {=} - --{ Initiator = { module = "Dummy", params = {=} } } + --, init_connections = [] : List { machine : Text, initiator : Text } + , init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] + , initiators = --{=} + { Initiator = { module = "Dummy", params = { uid = "Testuser" } } } , listens = [ { address = "127.0.0.1", port = Some 59661 } , { address = "::1", port = Some 59661 } diff --git a/src/api/machine.rs b/src/api/machine.rs index 65eb8d0..ab30a7f 100644 --- a/src/api/machine.rs +++ b/src/api/machine.rs @@ -6,46 +6,13 @@ use capnp::Error; use futures::FutureExt; -use crate::db::access::{PrivilegesBuf, PermRule}; +use crate::db::access::{PrivilegesBuf, PermRule, Perms}; use crate::db::user::UserId; use crate::db::machine::{Status, MachineState}; use crate::machine::Machine as NwMachine; use crate::schema::machine_capnp::machine::*; use crate::schema::machine_capnp::machine::MachineState as APIMState; -#[derive(Clone, Copy)] -pub struct Perms { - pub disclose: bool, - pub read: bool, - pub write: bool, - pub manage: bool, -} - -impl Perms { - pub fn get_for<'a, I: Iterator>(privs: &'a PrivilegesBuf, rules: I) -> Self { - let mut disclose = false; - let mut read = false; - let mut write = false; - let mut manage = false; - for rule in rules { - if rule.match_perm(&privs.disclose) { - disclose = true; - } - if rule.match_perm(&privs.read) { - read = true; - } - if rule.match_perm(&privs.write) { - write = true; - } - if rule.match_perm(&privs.manage) { - manage = true; - } - } - - Self { disclose, read, write, manage } - } -} - #[derive(Clone)] pub struct Machine { userid: UserId, diff --git a/src/api/machines.rs b/src/api/machines.rs index 6b1404a..878b57d 100644 --- a/src/api/machines.rs +++ b/src/api/machines.rs @@ -13,7 +13,7 @@ use crate::schema::machinesystem_capnp::machine_system; use crate::schema::machinesystem_capnp::machine_system::info as machines; use crate::network::Network; use crate::db::user::UserId; -use crate::db::access::{PermRule, admin_perm, Permission}; +use crate::db::access::{PermRule, admin_perm, Permission, Perms}; use crate::connection::Session; use crate::machine::Machine as NwMachine; diff --git a/src/config.rs b/src/config.rs index e83c665..b653c1b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,9 +5,9 @@ use std::collections::HashMap; use serde::{Serialize, Deserialize}; use crate::error::Result; -use crate::machine::MachineDescription; use crate::db::machine::MachineIdentifier; use crate::db::access::*; +use crate::machine::MachineDescription; pub fn read(path: &Path) -> Result { serde_dhall::from_file(path) diff --git a/src/db.rs b/src/db.rs index be802e6..5bec845 100644 --- a/src/db.rs +++ b/src/db.rs @@ -25,12 +25,13 @@ pub mod access; pub mod machine; pub type MachineDB = machine::internal::Internal; +pub type UserDB = user::Internal; #[derive(Clone)] pub struct Databases { pub access: Arc, pub machine: Arc, - pub userdb: Arc, + pub userdb: Arc, } const LMDB_MAX_DB: u32 = 16; diff --git a/src/db/access.rs b/src/db/access.rs index 159dbfb..813255d 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -11,6 +11,7 @@ use std::path::Path; use std::fs; use std::iter::FromIterator; use std::convert::{TryFrom, Into}; +use std::fmt::{Display, Formatter, Write}; use serde::{Serialize, Deserialize}; @@ -22,6 +23,81 @@ use crate::config::Config; use crate::db::user::UserData; pub use internal::Internal; +#[derive(Clone, Copy, Debug)] +pub struct Perms { + pub disclose: bool, + pub read: bool, + pub write: bool, + pub manage: bool, +} + +impl Display for Perms { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn part(f: &mut Formatter<'_>, fst: &mut bool, n: &'static str) -> fmt::Result { + if *fst { + *fst = false; + f.write_char(' ')?; + } else { + f.write_str("| ")?; + } + f.write_str(n)?; + f.write_char(' ') + } + + let mut fst = true; + f.write_char('(')?; + + if self.disclose { + part(f, &mut fst, "DISCLOSE")?; + } + if self.read { + part(f, &mut fst, "READ")?; + } + if self.write { + part(f, &mut fst, "WRITE")?; + } + if self.manage { + part(f, &mut fst, "MANAGE")?; + } + + f.write_char(')') + } +} + +impl Perms { + pub fn get_for<'a, I: Iterator>(privs: &'a PrivilegesBuf, rules: I) -> Self { + let mut disclose = false; + let mut read = false; + let mut write = false; + let mut manage = false; + for rule in rules { + if rule.match_perm(&privs.disclose) { + disclose = true; + } + if rule.match_perm(&privs.read) { + read = true; + } + if rule.match_perm(&privs.write) { + write = true; + } + if rule.match_perm(&privs.manage) { + manage = true; + } + } + + Self { disclose, read, write, manage } + } + + pub fn empty() -> Self { + Self { + disclose: false, + read: false, + write: false, + manage: false, + } + } +} + pub struct AccessControl { internal: HashMap, } @@ -566,6 +642,27 @@ impl TryFrom for PermRule { mod tests { use super::*; + #[test] + fn display_perm() { + let mut perm = Perms { + disclose: false, + read: false, + write: false, + manage: false + }; + println!("{}", perm); + assert_eq!("()", format!("{}", perm)); + + perm.disclose = true; + println!("{}", perm); + assert_eq!("( DISCLOSE )", format!("{}", perm)); + + perm.read = true; + perm.write = true; + println!("{}", perm); + assert_eq!("( DISCLOSE | READ | WRITE )", format!("{}", perm)); + } + #[test] fn permission_ord_test() { assert!(PermissionBuf::from_string("bffh.perm".to_string()) diff --git a/src/initiator.rs b/src/initiator.rs index dc8a190..39ca5d8 100644 --- a/src/initiator.rs +++ b/src/initiator.rs @@ -12,7 +12,7 @@ use paho_mqtt::AsyncClient; use futures::future::BoxFuture; use futures_signals::signal::{Signal, Mutable, MutableSignalCloned}; -use crate::machine::{Machine, ReturnToken}; +use crate::machine::Machine; use crate::db::machine::MachineState; use crate::db::user::{User, UserId, UserData}; @@ -22,7 +22,7 @@ use crate::error::Result; use crate::config::Config; pub trait Sensor { - fn run_sensor(&mut self) -> BoxFuture<'static, (Option, MachineState)>; + fn run_sensor(&mut self) -> BoxFuture<'static, (Option, MachineState)>; } type BoxSensor = Box; @@ -31,10 +31,9 @@ pub struct Initiator { log: Logger, signal: MutableSignalCloned>, machine: Option, - future: Option, MachineState)>>, + future: Option, MachineState)>>, // TODO: Prepare the init for async state change requests. - state_change_fut: Option>>, - token: Option, + state_change_fut: Option>>, sensor: BoxSensor, } @@ -46,7 +45,6 @@ impl Initiator { machine: None, future: None, state_change_fut: None, - token: None, sensor: sensor, } } @@ -92,13 +90,11 @@ impl Future for Initiator { debug!(this.log, "State change blocked"); return Poll::Pending; }, - Poll::Ready(Ok(tok)) => { + Poll::Ready(Ok(rt)) => { debug!(this.log, "State change returned ok"); // Explicity drop the future let _ = this.state_change_fut.take(); - // Store the given return token for future use - this.token.replace(tok); } Poll::Ready(Err(e)) => { info!(this.log, "State change returned err: {}", e); @@ -117,7 +113,7 @@ impl Future for Initiator { 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) + machine.request_state_change(user.as_ref(), state).unwrap() }); this.state_change_fut = f; } @@ -151,12 +147,12 @@ fn load_single( log: &Logger, name: &String, module_name: &String, - _params: &HashMap + params: &HashMap ) -> Option { match module_name.as_ref() { "Dummy" => { - Some(Box::new(Dummy::new(log))) + Some(Box::new(Dummy::new(log, params))) }, _ => { error!(log, "No initiator found with name \"{}\", configured as \"{}\"", @@ -169,34 +165,47 @@ fn load_single( pub struct Dummy { log: Logger, step: bool, + userid: Option, } impl Dummy { - pub fn new(log: &Logger) -> Self { - Self { log: log.new(o!("module" => "Dummy Initiator")), step: false } + pub fn new(log: &Logger, params: &HashMap) -> Self { + let userid = if let Some(uid) = params.get("uid") { + Some(UserId::new(uid.clone(), + params.get("subuid").map(String::from), + params.get("realm").map(String::from) + )) + } else { + None + }; + + let log = log.new(o!("module" => "Dummy Initiator")); + debug!(log, "Constructed dummy initiator with params: {:?}", params); + + Self { log, step: false, userid } + } } impl Sensor for Dummy { fn run_sensor(&mut self) - -> BoxFuture<'static, (Option, MachineState)> + -> BoxFuture<'static, (Option, MachineState)> { let step = self.step; self.step = !step; - info!(self.log, "Kicking off new dummy initiator state change: {}", step); + info!(self.log, "Kicking off new dummy initiator state change: {}, {:?}", + if step { "free" } else { "used" }, + &self.userid + ); + let userid = self.userid.clone(); let f = async move { Timer::after(std::time::Duration::from_secs(1)).await; if step { - return (None, MachineState::free()); + return (userid.clone(), MachineState::free()); } else { - let user = User::new( - UserId::new("test".to_string(), None, None), - 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))); + return (userid.clone(), MachineState::used(userid.clone())); } }; diff --git a/src/machine.rs b/src/machine.rs index d2dad26..255c669 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -23,10 +23,11 @@ use slog::Logger; use crate::error::{Result, Error}; -use crate::db::{access, Databases, MachineDB}; -use crate::db::access::AccessControl; +use crate::db::{access, Databases, MachineDB, UserDB}; +use crate::db::access::{AccessControl, Perms}; use crate::db::machine::{MachineIdentifier, MachineState, Status}; use crate::db::user::{User, UserData, UserId}; +use crate::Error::Denied; use crate::network::MachineMap; use crate::space; @@ -64,6 +65,8 @@ pub struct Machine { pub desc: MachineDescription, access_control: Arc, + userdb: Arc, + log: Logger, inner: Arc>, } @@ -73,7 +76,9 @@ impl Machine { inner: Inner, id: MachineIdentifier, desc: MachineDescription, - access_control: Arc + access_control: Arc, + userdb: Arc, + log: Logger, ) -> Self { Self { @@ -81,6 +86,8 @@ impl Machine { inner: Arc::new(Mutex::new(inner)), desc, access_control, + userdb, + log, } } @@ -90,9 +97,12 @@ impl Machine { state: MachineState, db: Arc, access_control: Arc, + userdb: Arc, + log: &Logger, ) -> Machine { - Self::new(Inner::new(id.clone(), state, db), id, desc, access_control) + let log = log.new(o!("machine" => id.clone())); + Self::new(Inner::new(id.clone(), state, db), id, desc, access_control, userdb, log) } pub fn do_state_change(&self, new_state: MachineState) @@ -109,13 +119,110 @@ impl Machine { Box::pin(f) } - pub fn request_state_change(&mut self, user: Option<&User>, new_state: MachineState) - -> BoxFuture<'static, Result> + pub fn request_state_change(&mut self, userid: Option<&UserId>, new_state: MachineState) + -> Result>> { let inner = self.inner.clone(); - Box::pin(async move { - Ok(ReturnToken::new(inner)) - }) + let perms = if let Some(userid) = userid { + if let Some(user) = self.userdb.get_user(&userid.uid)? { + let roles = self.access_control.collect_permrules(&user.data)?; + Perms::get_for(&self.desc.privs, roles.iter()) + } else { + Perms::empty() + } + } else { + Perms::empty() + }; + + debug!(self.log, "State change requested: {:?}, {:?}, {}", + &userid, + new_state, + perms, + ); + + if perms.manage { + Ok(Box::pin(async move { + let mut guard = inner.lock().await; + guard.do_state_change(new_state); + Ok(()) + })) + } else if perms.write { + let userid = userid.map(|u| u.clone()); + let f = async move { + let mut guard = inner.lock().await; + // Match (current state, new state) for permission + if + match (guard.read_state().lock_ref().state.clone(), &new_state.state) { + // Going from available to used by the person requesting is okay. + (Status::Free, Status::InUse(who)) + // Check that the person requesting does not request for somebody else. + // *That* is manage privilege. + if who.as_ref() == userid.as_ref() => true, + + // Reserving things for ourself is okay. + (Status::Free, Status::Reserved(whom)) + if userid.as_ref() == Some(whom) => true, + + // Returning things we've been using is okay. This includes both if + // they're being freed or marked as to be checked. + (Status::InUse(who), Status::Free | Status::ToCheck(_)) + if who.as_ref() == userid.as_ref() => true, + + // Un-reserving things we reserved is okay + (Status::Reserved(whom), Status::Free) + if userid.as_ref() == Some(&whom) => true, + // Using things that we've reserved is okay. But the person requesting + // that has to be the person that reserved the machine. Otherwise + // somebody could make a machine reserved by a different user as used by + // that different user but use it themself. + (Status::Reserved(whom), Status::InUse(who)) + if userid.as_ref() == Some(&whom) + && who.as_ref() == Some(&whom) + => true, + + // Default is deny. + _ => false + } + { + // Permission granted + guard.do_state_change(new_state); + Ok(()) + } else { + Err(Denied) + } + }; + + Ok(Box::pin(f)) + } else { + let userid = userid.map(|u| u.clone()); + let f = async move { + let mut guard = inner.lock().await; + // Match (current state, new state) for permission + if + match (guard.read_state().lock_ref().state.clone(), &new_state.state) { + // Returning things we've been using is okay. This includes both if + // they're being freed or marked as to be checked. + (Status::InUse(who), Status::Free | Status::ToCheck(_)) + if who.as_ref() == userid.as_ref() => true, + + // Un-reserving things we reserved is okay + (Status::Reserved(whom), Status::Free) + if userid.as_ref() == Some(&whom) => true, + + // Default is deny. + _ => false + } + { + // Permission granted + guard.do_state_change(new_state); + Ok(()) + } else { + Err(Denied) + } + }; + + Ok(Box::pin(f)) + } } pub async fn get_status(&self) -> Status { @@ -244,39 +351,6 @@ impl Inner { } } -//pub type ReturnToken = futures::channel::oneshot::Sender<()>; -pub struct ReturnToken { - f: Option>, -} - -impl ReturnToken { - pub fn new(inner: Arc>) -> Self { - let f = async move { - let mut guard = inner.lock().await; - guard.reset_state(); - }; - - Self { f: Some(Box::pin(f)) } - } -} - -impl Future for ReturnToken { - type Output = (); // FIXME: This should probably be a Result<(), Error> - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let mut this = &mut *self; - - match this.f.as_mut().map(|f| Future::poll(Pin::new(f), cx)) { - None => Poll::Ready(()), // TODO: Is it saner to return Pending here? This can only happen after the future completed - Some(Poll::Pending) => Poll::Pending, - Some(Poll::Ready(())) => { - let _ = this.f.take(); // Remove the future to not poll after completion - Poll::Ready(()) - } - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] /// A description of a machine /// @@ -305,24 +379,27 @@ impl MachineDescription { } pub fn load(config: &crate::config::Config, db: Databases, log: &Logger) - -> Result + -> Result { let mut map = config.machines.clone(); let access_control = db.access; - let db = db.machine; + let machinedb = db.machine; + let userdb = db.userdb; let it = map.drain() .map(|(k,v)| { // TODO: Read state from the state db - if let Some(state) = db.get(&k).unwrap() { + if let Some(state) = machinedb.get(&k).unwrap() { debug!(log, "Loading old state from db for {}: {:?}", &k, &state); (k.clone(), Machine::construct( - k, - v, - state, - db.clone(), - access_control.clone() + k, + v, + state, + machinedb.clone(), + access_control.clone(), + userdb.clone(), + log, )) } else { debug!(log, "No old state found in db for {}, creating new.", &k); @@ -331,8 +408,10 @@ pub fn load(config: &crate::config::Config, db: Databases, log: &Logger) k, v, MachineState::new(), - db.clone(), + machinedb.clone(), access_control.clone(), + userdb.clone(), + log, )) } });