This commit is contained in:
Nadja Reitzenstein 2021-12-07 23:02:26 +01:00
parent 6b88191dc5
commit a09c3d3880
8 changed files with 269 additions and 116 deletions

View File

@ -8,7 +8,7 @@
, { machine = "Yetmore", actor = "FailBash"} , { machine = "Yetmore", actor = "FailBash"}
] ]
, actors = , actors =
{ Shelly_1234 = { module = "Shelly", params = { Shelly1234 = { module = "Shelly", params =
{ topic = "Topic1234" }} { topic = "Topic1234" }}
, Bash = { module = "Process", params = , Bash = { module = "Process", params =
{ cmd = "./examples/actor.sh" { cmd = "./examples/actor.sh"
@ -22,10 +22,10 @@
{ cmd = "./examples/fail-actor.sh" { cmd = "./examples/fail-actor.sh"
}} }}
} }
, init_connections = [] : List { machine : Text, initiator : Text } --, init_connections = [] : List { machine : Text, initiator : Text }
--, init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] , init_connections = [{ machine = "Testmachine", initiator = "Initiator" }]
, initiators = {=} , initiators = --{=}
--{ Initiator = { module = "Dummy", params = {=} } } { Initiator = { module = "Dummy", params = { uid = "Testuser" } } }
, listens = , listens =
[ { address = "127.0.0.1", port = Some 59661 } [ { address = "127.0.0.1", port = Some 59661 }
, { address = "::1", port = Some 59661 } , { address = "::1", port = Some 59661 }

View File

@ -6,46 +6,13 @@ use capnp::Error;
use futures::FutureExt; use futures::FutureExt;
use crate::db::access::{PrivilegesBuf, PermRule}; use crate::db::access::{PrivilegesBuf, PermRule, Perms};
use crate::db::user::UserId; use crate::db::user::UserId;
use crate::db::machine::{Status, MachineState}; use crate::db::machine::{Status, MachineState};
use crate::machine::Machine as NwMachine; use crate::machine::Machine as NwMachine;
use crate::schema::machine_capnp::machine::*; use crate::schema::machine_capnp::machine::*;
use crate::schema::machine_capnp::machine::MachineState as APIMState; 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<Item=&'a PermRule>>(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)] #[derive(Clone)]
pub struct Machine { pub struct Machine {
userid: UserId, userid: UserId,

View File

@ -13,7 +13,7 @@ use crate::schema::machinesystem_capnp::machine_system;
use crate::schema::machinesystem_capnp::machine_system::info as machines; use crate::schema::machinesystem_capnp::machine_system::info as machines;
use crate::network::Network; use crate::network::Network;
use crate::db::user::UserId; 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::connection::Session;
use crate::machine::Machine as NwMachine; use crate::machine::Machine as NwMachine;

View File

@ -5,9 +5,9 @@ use std::collections::HashMap;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::error::Result; use crate::error::Result;
use crate::machine::MachineDescription;
use crate::db::machine::MachineIdentifier; use crate::db::machine::MachineIdentifier;
use crate::db::access::*; use crate::db::access::*;
use crate::machine::MachineDescription;
pub fn read(path: &Path) -> Result<Config> { pub fn read(path: &Path) -> Result<Config> {
serde_dhall::from_file(path) serde_dhall::from_file(path)

View File

@ -25,12 +25,13 @@ pub mod access;
pub mod machine; pub mod machine;
pub type MachineDB = machine::internal::Internal; pub type MachineDB = machine::internal::Internal;
pub type UserDB = user::Internal;
#[derive(Clone)] #[derive(Clone)]
pub struct Databases { pub struct Databases {
pub access: Arc<access::AccessControl>, pub access: Arc<access::AccessControl>,
pub machine: Arc<MachineDB>, pub machine: Arc<MachineDB>,
pub userdb: Arc<user::Internal>, pub userdb: Arc<UserDB>,
} }
const LMDB_MAX_DB: u32 = 16; const LMDB_MAX_DB: u32 = 16;

View File

@ -11,6 +11,7 @@ use std::path::Path;
use std::fs; use std::fs;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::convert::{TryFrom, Into}; use std::convert::{TryFrom, Into};
use std::fmt::{Display, Formatter, Write};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
@ -22,6 +23,81 @@ use crate::config::Config;
use crate::db::user::UserData; use crate::db::user::UserData;
pub use internal::Internal; 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<Item=&'a PermRule>>(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 { pub struct AccessControl {
internal: HashMap<RoleIdentifier, Role>, internal: HashMap<RoleIdentifier, Role>,
} }
@ -566,6 +642,27 @@ impl TryFrom<String> for PermRule {
mod tests { mod tests {
use super::*; 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] #[test]
fn permission_ord_test() { fn permission_ord_test() {
assert!(PermissionBuf::from_string("bffh.perm".to_string()) assert!(PermissionBuf::from_string("bffh.perm".to_string())

View File

@ -12,7 +12,7 @@ use paho_mqtt::AsyncClient;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures_signals::signal::{Signal, Mutable, MutableSignalCloned}; use futures_signals::signal::{Signal, Mutable, MutableSignalCloned};
use crate::machine::{Machine, ReturnToken}; use crate::machine::Machine;
use crate::db::machine::MachineState; use crate::db::machine::MachineState;
use crate::db::user::{User, UserId, UserData}; use crate::db::user::{User, UserId, UserData};
@ -22,7 +22,7 @@ use crate::error::Result;
use crate::config::Config; use crate::config::Config;
pub trait Sensor { pub trait Sensor {
fn run_sensor(&mut self) -> BoxFuture<'static, (Option<User>, MachineState)>; fn run_sensor(&mut self) -> BoxFuture<'static, (Option<UserId>, MachineState)>;
} }
type BoxSensor = Box<dyn Sensor + Send>; type BoxSensor = Box<dyn Sensor + Send>;
@ -31,10 +31,9 @@ pub struct Initiator {
log: Logger, 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<UserId>, MachineState)>>,
// TODO: Prepare the init for async state change requests. // TODO: Prepare the init for async state change requests.
state_change_fut: Option<BoxFuture<'static, Result<ReturnToken>>>, state_change_fut: Option<BoxFuture<'static, Result<()>>>,
token: Option<ReturnToken>,
sensor: BoxSensor, sensor: BoxSensor,
} }
@ -46,7 +45,6 @@ impl Initiator {
machine: None, machine: None,
future: None, future: None,
state_change_fut: None, state_change_fut: None,
token: None,
sensor: sensor, sensor: sensor,
} }
} }
@ -92,13 +90,11 @@ impl Future for Initiator {
debug!(this.log, "State change blocked"); debug!(this.log, "State change blocked");
return Poll::Pending; return Poll::Pending;
}, },
Poll::Ready(Ok(tok)) => { Poll::Ready(Ok(rt)) => {
debug!(this.log, "State change 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();
// Store the given return token for future use
this.token.replace(tok);
} }
Poll::Ready(Err(e)) => { Poll::Ready(Err(e)) => {
info!(this.log, "State change returned 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"); 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).unwrap()
}); });
this.state_change_fut = f; this.state_change_fut = f;
} }
@ -151,12 +147,12 @@ fn load_single(
log: &Logger, log: &Logger,
name: &String, name: &String,
module_name: &String, module_name: &String,
_params: &HashMap<String, String> params: &HashMap<String, String>
) -> Option<BoxSensor> ) -> Option<BoxSensor>
{ {
match module_name.as_ref() { match module_name.as_ref() {
"Dummy" => { "Dummy" => {
Some(Box::new(Dummy::new(log))) Some(Box::new(Dummy::new(log, params)))
}, },
_ => { _ => {
error!(log, "No initiator found with name \"{}\", configured as \"{}\"", error!(log, "No initiator found with name \"{}\", configured as \"{}\"",
@ -169,34 +165,47 @@ fn load_single(
pub struct Dummy { pub struct Dummy {
log: Logger, log: Logger,
step: bool, step: bool,
userid: Option<UserId>,
} }
impl Dummy { impl Dummy {
pub fn new(log: &Logger) -> Self { pub fn new(log: &Logger, params: &HashMap<String, String>) -> Self {
Self { log: log.new(o!("module" => "Dummy Initiator")), step: false } 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 { impl Sensor for Dummy {
fn run_sensor(&mut self) fn run_sensor(&mut self)
-> BoxFuture<'static, (Option<User>, MachineState)> -> BoxFuture<'static, (Option<UserId>, MachineState)>
{ {
let step = self.step; let step = self.step;
self.step = !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 { let f = async move {
Timer::after(std::time::Duration::from_secs(1)).await; Timer::after(std::time::Duration::from_secs(1)).await;
if step { if step {
return (None, MachineState::free()); return (userid.clone(), MachineState::free());
} else { } else {
let user = User::new( return (userid.clone(), MachineState::used(userid.clone()));
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)));
} }
}; };

View File

@ -23,10 +23,11 @@ use slog::Logger;
use crate::error::{Result, Error}; use crate::error::{Result, Error};
use crate::db::{access, Databases, MachineDB}; use crate::db::{access, Databases, MachineDB, UserDB};
use crate::db::access::AccessControl; use crate::db::access::{AccessControl, Perms};
use crate::db::machine::{MachineIdentifier, MachineState, Status}; use crate::db::machine::{MachineIdentifier, MachineState, Status};
use crate::db::user::{User, UserData, UserId}; use crate::db::user::{User, UserData, UserId};
use crate::Error::Denied;
use crate::network::MachineMap; use crate::network::MachineMap;
use crate::space; use crate::space;
@ -64,6 +65,8 @@ pub struct Machine {
pub desc: MachineDescription, pub desc: MachineDescription,
access_control: Arc<AccessControl>, access_control: Arc<AccessControl>,
userdb: Arc<UserDB>,
log: Logger,
inner: Arc<Mutex<Inner>>, inner: Arc<Mutex<Inner>>,
} }
@ -73,7 +76,9 @@ impl Machine {
inner: Inner, inner: Inner,
id: MachineIdentifier, id: MachineIdentifier,
desc: MachineDescription, desc: MachineDescription,
access_control: Arc<AccessControl> access_control: Arc<AccessControl>,
userdb: Arc<UserDB>,
log: Logger,
) -> Self ) -> Self
{ {
Self { Self {
@ -81,6 +86,8 @@ impl Machine {
inner: Arc::new(Mutex::new(inner)), inner: Arc::new(Mutex::new(inner)),
desc, desc,
access_control, access_control,
userdb,
log,
} }
} }
@ -90,9 +97,12 @@ impl Machine {
state: MachineState, state: MachineState,
db: Arc<MachineDB>, db: Arc<MachineDB>,
access_control: Arc<AccessControl>, access_control: Arc<AccessControl>,
userdb: Arc<UserDB>,
log: &Logger,
) -> Machine ) -> 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) pub fn do_state_change(&self, new_state: MachineState)
@ -109,13 +119,110 @@ impl Machine {
Box::pin(f) Box::pin(f)
} }
pub fn request_state_change(&mut self, user: Option<&User>, new_state: MachineState) pub fn request_state_change(&mut self, userid: Option<&UserId>, new_state: MachineState)
-> BoxFuture<'static, Result<ReturnToken>> -> Result<BoxFuture<'static, Result<()>>>
{ {
let inner = self.inner.clone(); let inner = self.inner.clone();
Box::pin(async move { let perms = if let Some(userid) = userid {
Ok(ReturnToken::new(inner)) 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 { 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<BoxFuture<'static, ()>>,
}
impl ReturnToken {
pub fn new(inner: Arc<Mutex<Inner>>) -> 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<Self::Output> {
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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
/// A description of a machine /// A description of a machine
/// ///
@ -309,20 +383,23 @@ pub fn load(config: &crate::config::Config, db: Databases, log: &Logger)
{ {
let mut map = config.machines.clone(); let mut map = config.machines.clone();
let access_control = db.access; let access_control = db.access;
let db = db.machine; let machinedb = db.machine;
let userdb = db.userdb;
let it = map.drain() let it = map.drain()
.map(|(k,v)| { .map(|(k,v)| {
// TODO: Read state from the state db // 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); debug!(log, "Loading old state from db for {}: {:?}", &k, &state);
(k.clone(), (k.clone(),
Machine::construct( Machine::construct(
k, k,
v, v,
state, state,
db.clone(), machinedb.clone(),
access_control.clone() access_control.clone(),
userdb.clone(),
log,
)) ))
} else { } else {
debug!(log, "No old state found in db for {}, creating new.", &k); 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, k,
v, v,
MachineState::new(), MachineState::new(),
db.clone(), machinedb.clone(),
access_control.clone(), access_control.clone(),
userdb.clone(),
log,
)) ))
} }
}); });