This commit is contained in:
Nadja Reitzenstein 2021-09-30 10:07:42 +02:00
parent 0a9ae09984
commit 65830af01d
6 changed files with 157 additions and 45 deletions

View File

@ -49,6 +49,21 @@ impl AccessControl {
} }
} }
pub fn check<P: AsRef<Permission>>(&self, user: &UserData, perm: P) -> Result<bool> {
let mut roles = HashMap::new();
// Check all user roles by..
Ok(user.roles.iter().any(|role| {
// 1. Getting the whole tree down to a list of Roles applied
self.internal.tally_role(&mut roles, role)?;
// 2. Checking if any of the roles the user has give any permission granting the
// requested one.
roles.drain().any(|(rid, role)| {
role.permissions.iter().any(|rule| rule.match_perm(perm))
})
}))
}
pub fn collect_permrules(&self, user: &UserData) -> Result<Vec<PermRule>> { pub fn collect_permrules(&self, user: &UserData) -> Result<Vec<PermRule>> {
self.internal.collect_permrules(user) self.internal.collect_permrules(user)
} }

View File

@ -1,3 +1,4 @@
use std::sync::Arc;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use std::future::Future; use std::future::Future;
@ -7,14 +8,13 @@ use smol::Timer;
use slog::Logger; use slog::Logger;
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, ReturnToken};
use crate::db::machine::MachineState; use crate::db::machine::MachineState;
use crate::db::user::{User, UserId, UserData}; use crate::db::user::{User, UserId, UserData, Internal as UserDB};
use crate::db::access::AccessControl;
use crate::network::InitMap; use crate::network::InitMap;
@ -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>;
@ -36,10 +36,13 @@ pub struct Initiator {
state_change_fut: Option<BoxFuture<'static, Result<ReturnToken>>>, state_change_fut: Option<BoxFuture<'static, Result<ReturnToken>>>,
token: Option<ReturnToken>, token: Option<ReturnToken>,
sensor: BoxSensor, sensor: BoxSensor,
userdb: UserDB,
access: AccessControl,
} }
impl Initiator { impl Initiator {
pub fn new(log: Logger, sensor: BoxSensor, signal: MutableSignalCloned<Option<Machine>>) -> Self { pub fn new(log: Logger, sensor: BoxSensor, signal: MutableSignalCloned<Option<Machine>>, userdb: UserDB, access: AccessControl) -> Self {
Self { Self {
log: log, log: log,
signal: signal, signal: signal,
@ -48,14 +51,16 @@ impl Initiator {
state_change_fut: None, state_change_fut: None,
token: None, token: None,
sensor: sensor, sensor: sensor,
userdb,
access,
} }
} }
pub fn wrap(log: Logger, sensor: BoxSensor) -> (Mutable<Option<Machine>>, Self) { pub fn wrap(log: Logger, sensor: BoxSensor, userdb: UserDB, access: Arc<AccessControl>) -> (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(log, sensor, s)) (m, Self::new(log, sensor, s, userdb, access))
} }
} }
@ -113,12 +118,11 @@ impl Future for Initiator {
None => { None => {
this.future = Some(this.sensor.run_sensor()); this.future = Some(this.sensor.run_sensor());
}, },
Some(Poll::Ready((user, state))) => { Some(Poll::Ready((uid, state))) => {
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| {
unimplemented!() machine.request_state_change(state, this.access.clone(), user)
//machine.request_state_change(user.as_ref(), state)
}); });
this.state_change_fut = f; this.state_change_fut = f;
} }
@ -128,7 +132,7 @@ impl Future for Initiator {
} }
} }
pub fn load(log: &Logger, config: &Config) -> Result<(InitMap, Vec<Initiator>)> { pub fn load(log: &Logger, config: &Config, userdb: UserDB, access: Arc<AccessControl>) -> Result<(InitMap, Vec<Initiator>)> {
let mut map = HashMap::new(); let mut map = HashMap::new();
let initiators = config.initiators.iter() let initiators = config.initiators.iter()
@ -140,7 +144,7 @@ pub fn load(log: &Logger, config: &Config) -> Result<(InitMap, Vec<Initiator>)>
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(log.new(o!("name" => name.clone())), initiator); let (m, i) = Initiator::wrap(log.new(o!("name" => name.clone())), initiator, userdb.clone(), access.clone());
map.insert(name.clone(), m); map.insert(name.clone(), m);
v.push(i); v.push(i);
} }
@ -180,7 +184,7 @@ impl Dummy {
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;
@ -192,12 +196,8 @@ impl Sensor for Dummy {
if step { if step {
return (None, MachineState::free()); return (None, MachineState::free());
} else { } else {
let user = User::new( let user = UserId::new("test".to_string(), None, None);
UserId::new("test".to_string(), None, None), return (Some(user), MachineState::used(Some(user)));
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

@ -22,9 +22,9 @@ use futures_signals::signal::{Mutable, ReadOnlyMutable};
use crate::error::{Result, Error}; use crate::error::{Result, Error};
use crate::db::access; use crate::db::access::{AccessControl, PrivilegesBuf, PermissionBuf};
use crate::db::machine::{MachineIdentifier, MachineState, Status}; use crate::db::machine::{MachineIdentifier, MachineState, Status};
use crate::db::user::{User, UserData}; use crate::db::user::{User, UserData, UserId};
use crate::network::MachineMap; use crate::network::MachineMap;
use crate::space; use crate::space;
@ -82,6 +82,52 @@ impl Machine {
Self::new(Inner::new(id, state), desc) Self::new(Inner::new(id, state), desc)
} }
fn match_perm(&self, status: &Status) -> Option<&PermissionBuf> {
let p = self.desc.privs;
match status {
// If you were allowed to use it you're allowed to give it back
Status::Free
| Status::ToCheck(_)
=> None,
Status::Blocked(_)
| Status::Disabled
| Status::Reserved(_)
=> Some(&p.manage),
Status::InUse(_) => Some(&p.write),
}
}
pub fn request_state_change(&self, new_state: MachineState, access: AccessControl, user: &User)
-> BoxFuture<'static, Result<()>>
{
let this = self.clone();
let perm = self.match_perm(&new_state.state);
let grant = perm.map(|p| access.check(&user.data, p).unwrap_or(false));
let uid = user.id.clone();
// is it a return
let is_ret = new_state.state == Status::Free;
// is it a (normal) write /the user is allowed to do/?
let is_wri = new_state.state == Status::InUse(Some(uid))
&& access.check(&user.data, self.desc.privs.write).unwrap_or(false);
let f = async move {
let mut guard = this.inner.lock().await;
// either e.g. InUse(<myself>) => Free or I'm allowed to overwrite
if (is_ret && guard.is_self(uid))
|| (is_wri && guard.is_free())
|| grant.unwrap_or(false)
{
guard.do_state_change(new_state);
}
return Ok(())
};
Box::pin(f)
}
pub fn do_state_change(&self, new_state: MachineState) pub fn do_state_change(&self, new_state: MachineState)
-> BoxFuture<'static, Result<()>> -> BoxFuture<'static, Result<()>>
{ {
@ -126,6 +172,10 @@ impl Deref for Machine {
/// A machine connects an event from a sensor to an actor activating/deactivating a real-world /// A machine connects an event from a sensor to an actor activating/deactivating a real-world
/// machine, checking that the user who wants the machine (de)activated has the required /// machine, checking that the user who wants the machine (de)activated has the required
/// permissions. /// permissions.
///
/// Machines have a rather complex state machine since they have to be eventually consistent and
/// can fail at any point in time (e.g. because power cuts out suddenly, a different task on this
/// thread panics, some loaded code produces a segfault, ...)
pub struct Inner { pub struct Inner {
/// Globally unique machine readable identifier /// Globally unique machine readable identifier
pub id: MachineIdentifier, pub id: MachineIdentifier,
@ -180,6 +230,20 @@ impl Inner {
self.state.replace(state); self.state.replace(state);
} }
} }
pub fn is_self(&mut self, uid: UserId) -> bool {
match self.read_state().get_cloned().state {
Status::InUse(u) if u == uid => true,
_ => false,
}
}
pub fn is_free(&mut self) -> bool {
match self.read_state().get_cloned().state {
Status::Free => true,
_ => false,
}
}
} }
//pub type ReturnToken = futures::channel::oneshot::Sender<()>; //pub type ReturnToken = futures::channel::oneshot::Sender<()>;
@ -228,7 +292,7 @@ pub struct MachineDescription {
/// The permission required /// The permission required
#[serde(flatten)] #[serde(flatten)]
pub privs: access::PrivilegesBuf, pub privs: PrivilegesBuf,
} }
impl MachineDescription { impl MachineDescription {

View File

@ -25,6 +25,8 @@ mod actor;
mod initiator; mod initiator;
mod space; mod space;
mod resource;
use clap::{App, Arg}; use clap::{App, Arg};
use std::io; use std::io;
@ -169,7 +171,7 @@ fn maybe(matches: clap::ArgMatches, log: Arc<Logger>) -> Result<(), Error> {
let machines = machine::load(&config)?; let machines = machine::load(&config)?;
let (actor_map, actors) = actor::load(&log, &config)?; let (actor_map, actors) = actor::load(&log, &config)?;
let (init_map, initiators) = initiator::load(&log, &config)?; let (init_map, initiators) = initiator::load(&log, &config, db.userdb.clone(), db.access.clone())?;
let mut network = network::Network::new(machines, actor_map, init_map); let mut network = network::Network::new(machines, actor_map, init_map);

View File

@ -1,12 +1,13 @@
use std::io::{Read, Write};
use std::pin::Pin; use std::pin::Pin;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::process::Stdio; use std::process::Stdio;
use smol::process::{Command, Child}; use smol::process::{Command, Child};
use smol::io::{AsyncWriteExt, AsyncReadExt}; use smol::io::{AsyncWrite, AsyncWriteExt, AsyncReadExt};
use futures::future::FutureExt; use futures::future::{Future, FutureExt};
use crate::actor::Actuator; use crate::actor::Actuator;
use crate::initiator::Sensor; use crate::initiator::Sensor;
@ -19,13 +20,14 @@ use slog::Logger;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
pub struct Batch { pub struct Batch {
log: Logger,
userdb: UserDB, userdb: UserDB,
name: String, name: String,
cmd: String, cmd: String,
args: Vec<String>, args: Vec<String>,
kill: bool, kill: bool,
child: Child, child: Child,
stdout: RefCell<Pin<Box<dyn AsyncWrite>>>, stdout: Pin<Box<dyn AsyncWrite>>,
} }
impl Batch { impl Batch {
@ -39,24 +41,28 @@ impl Batch {
.collect()) .collect())
.unwrap_or_else(Vec::new); .unwrap_or_else(Vec::new);
let kill = params.get("kill_on_exit").and_then(|s| let kill = params
s.parse() .get("kill_on_exit")
.or_else(|| { .and_then(|kill|
kill.parse()
.or_else(|_| {
warn!(log, "Can't parse `kill_on_exit` for {} set as {} as boolean. \ warn!(log, "Can't parse `kill_on_exit` for {} set as {} as boolean. \
Must be either \"True\" or \"False\".", &name, &s); Must be either \"True\" or \"False\".", &name, &s);
false Ok(false)
})); })
.ok())
.unwrap_or(false);
info!(log, "Starting {} ({})…", &name, &cmd); info!(log, "Starting {} ({})…", &name, &cmd);
let mut child = Self::start(&name, &cmd, &args) let mut child = Self::start(&name, &cmd, &args)
.map_err(|err| error!(log, "Failed to spawn {} ({}): {}", &name, &cmd, err)) .map_err(|err| error!(log, "Failed to spawn {} ({}): {}", &name, &cmd, err))
.ok()?; .ok()?;
let stdout = Self::get_stdin(&mut child); let stdout = Self::get_stdout(&mut child);
Ok(Self { userdb, name, cmd, args, kill, child, stdout }) Ok(Self { log, userdb, name, cmd, args, kill, child, stdout })
} }
fn start_actor(name: &String, cmd: &String, args: &Vec<String>) -> Result<Child> { fn start(name: &String, cmd: &String, args: &Vec<String>) -> std::io::Result<Child> {
let mut command = Command::new(cmd); let mut command = Command::new(cmd);
command command
.stdin(Stdio::piped()) .stdin(Stdio::piped())
@ -74,7 +80,7 @@ impl Batch {
stdout.boxed_writer() stdout.boxed_writer()
} }
fn maybe_restart(&mut self, f: &mut Option<impl Future<Item=()>>) -> bool { fn maybe_restart(&mut self, f: &mut Option<BoxFuture<'static, ()>>) -> bool {
let stat = self.child.try_status(); let stat = self.child.try_status();
if stat.is_err() { if stat.is_err() {
error!(self.log, "Can't check process for {} ({}) [{}]: {}", error!(self.log, "Can't check process for {} ({}) [{}]: {}",
@ -87,22 +93,22 @@ impl Batch {
let errlog = self.log.new(o!("pid" => self.child.id())); let errlog = self.log.new(o!("pid" => self.child.id()));
// If we have any stderr try to log it // If we have any stderr try to log it
if let Some(stderr) = self.child.stderr.take() { if let Some(stderr) = self.child.stderr.take() {
f = Some(async move { *f = Some(Box::pin(async move {
match stderr.into_stdio().await { let mut out = String::new();
Err(err) => error!(errlog, "Failed to open actor process STDERR: ", err), match stderr.read_to_string(&mut out).await {
Ok(err) => if !retv.stderr.is_empty() { Err(e) => warn!(errlog, "Failed to read child stderr: {}", e),
let errstr = String::from_utf8_lossy(err); Ok(n) => if n != 0 {
let errstr = String::from_utf8_lossy(out);
for line in errstr.lines() { for line in errstr.lines() {
warn!(errlog, "{}", line); warn!(errlog, "{}", line);
} }
} }
_ => {}
} }
}); }));
} }
info!(self.log, "Attempting to re-start {}", &self.name); info!(self.log, "Attempting to re-start {}", &self.name);
let mut child = Self::start(&self.name, &self.cmd, &self.args) let mut child = Self::start(&self.name, &self.cmd, &self.args)
.map_err(|err| error!(log, "Failed to spawn {} ({}): {}", &self.name, &self.cmd, err)) .map_err(|err| error!(self.log, "Failed to spawn {} ({}): {}", &self.name, &self.cmd, err))
.ok(); .ok();
// Nothing else to do with the currect architecture. In reality we should fail here // Nothing else to do with the currect architecture. In reality we should fail here
// because we *didn't apply* the change. // because we *didn't apply* the change.

25
src/resource.rs Normal file
View File

@ -0,0 +1,25 @@
use core::sync::atomic;
/// A something BFFH holds internal state of
pub struct Resource {
// claims
strong: atomic::AtomicUsize,
weak: atomic::AtomicUsize,
max_strong: usize,
}
/// A claim is taken in lieu of an user on a resource.
///
/// They come in two flavours: Weak, of which an infinite amount can exist, and Strong which may be
/// limited in number. Strong claims represent the right of the user to use this resource
/// "writable". A weak claim indicates co-usage of a resource and are mainly useful for notice and
/// information of the respective other ones. E.g. a space would be strongly claimed by keyholders
/// when they check in and released when they check out and weakly claimed by everybody else. In
/// that case the last strong claim could also fail to be released if there are outstanding weak
/// claims. Alternatively, releasing the last strong claim also releases all weak claims and sets
/// the resource to "Free" again.
///
/// Most importantly, claims can be released by *both* the claim holder and the resource.
pub struct Claim {
id: u128,
}