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>> {
self.internal.collect_permrules(user)
}

View File

@ -1,3 +1,4 @@
use std::sync::Arc;
use std::pin::Pin;
use std::task::{Poll, Context};
use std::future::Future;
@ -7,14 +8,13 @@ use smol::Timer;
use slog::Logger;
use paho_mqtt::AsyncClient;
use futures::future::BoxFuture;
use futures_signals::signal::{Signal, Mutable, MutableSignalCloned};
use crate::machine::{Machine, ReturnToken};
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;
@ -22,7 +22,7 @@ use crate::error::Result;
use crate::config::Config;
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>;
@ -36,10 +36,13 @@ pub struct Initiator {
state_change_fut: Option<BoxFuture<'static, Result<ReturnToken>>>,
token: Option<ReturnToken>,
sensor: BoxSensor,
userdb: UserDB,
access: AccessControl,
}
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 {
log: log,
signal: signal,
@ -48,14 +51,16 @@ impl Initiator {
state_change_fut: None,
token: None,
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 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 => {
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");
this.future.take();
let f = this.machine.as_mut().map(|machine| {
unimplemented!()
//machine.request_state_change(user.as_ref(), state)
machine.request_state_change(state, this.access.clone(), user)
});
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 initiators = config.initiators.iter()
@ -140,7 +144,7 @@ pub fn load(log: &Logger, config: &Config) -> Result<(InitMap, Vec<Initiator>)>
let mut v = Vec::new();
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);
v.push(i);
}
@ -180,7 +184,7 @@ impl Dummy {
impl Sensor for Dummy {
fn run_sensor(&mut self)
-> BoxFuture<'static, (Option<User>, MachineState)>
-> BoxFuture<'static, (Option<UserId>, MachineState)>
{
let step = self.step;
self.step = !step;
@ -192,12 +196,8 @@ impl Sensor for Dummy {
if step {
return (None, 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)));
let user = UserId::new("test".to_string(), None, None);
return (Some(user), MachineState::used(Some(user)));
}
};

View File

@ -22,9 +22,9 @@ use futures_signals::signal::{Mutable, ReadOnlyMutable};
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::user::{User, UserData};
use crate::db::user::{User, UserData, UserId};
use crate::network::MachineMap;
use crate::space;
@ -82,6 +82,52 @@ impl Machine {
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)
-> 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
/// machine, checking that the user who wants the machine (de)activated has the required
/// 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 {
/// Globally unique machine readable identifier
pub id: MachineIdentifier,
@ -180,6 +230,20 @@ impl Inner {
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<()>;
@ -228,7 +292,7 @@ pub struct MachineDescription {
/// The permission required
#[serde(flatten)]
pub privs: access::PrivilegesBuf,
pub privs: PrivilegesBuf,
}
impl MachineDescription {

View File

@ -25,6 +25,8 @@ mod actor;
mod initiator;
mod space;
mod resource;
use clap::{App, Arg};
use std::io;
@ -169,7 +171,7 @@ fn maybe(matches: clap::ArgMatches, log: Arc<Logger>) -> Result<(), Error> {
let machines = machine::load(&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);

View File

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