mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2025-04-20 03:06:26 +02:00
Compare commits
10 Commits
developmen
...
release/in
Author | SHA1 | Date | |
---|---|---|---|
|
3a16d1031b | ||
|
7aa808a3fe | ||
|
bed59f4aaa | ||
|
d196050fe0 | ||
|
198845f176 | ||
|
386ac5645d | ||
|
98ed9efec9 | ||
|
1602879e1e | ||
|
5fdfae295a | ||
|
b8092e9090 |
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,5 +1,23 @@
|
||||
# Revision history for Difluoroborane
|
||||
|
||||
## 0.4.3 -- 2023-03-17
|
||||
Final INTERFACER Rlease
|
||||
|
||||
### Features
|
||||
* improved API
|
||||
* authenticate users with SmartCard
|
||||
* access Machines via SmartCard
|
||||
* log data of machines in Audit-Log
|
||||
|
||||
### Updates
|
||||
* authenticate users via FabReader and the FabFire-Adapter
|
||||
* pass aditional data form users via server to actors
|
||||
* provide new interface to handle lockers
|
||||
|
||||
## 0.4.2 -- 2022-10-07
|
||||
Beta Release of INTERFACER Project.
|
||||
|
||||
|
||||
## 0.4.1 -- 2022-04-24
|
||||
|
||||
* Initial full implementation of the FabAccess 0.3 API, "Spigots of Berlin".
|
||||
|
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -425,6 +425,12 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "bitfield"
|
||||
version = "0.13.2"
|
||||
@ -999,6 +1005,7 @@ dependencies = [
|
||||
"async-process",
|
||||
"async-trait",
|
||||
"backtrace",
|
||||
"base64 0.21.0",
|
||||
"capnp",
|
||||
"capnp-rpc",
|
||||
"chrono",
|
||||
@ -1467,7 +1474,7 @@ version = "7.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea9fe3952d32674a14e0975009a3547af9ea364995b5ec1add2e23c2ae523ab"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
"byteorder",
|
||||
"crossbeam-channel",
|
||||
"flate2",
|
||||
@ -2541,7 +2548,7 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
"blake2b_simd",
|
||||
"constant_time_eq",
|
||||
"crossbeam-utils",
|
||||
@ -2583,7 +2590,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2592,7 +2599,7 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3135,7 +3142,7 @@ dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64",
|
||||
"base64 0.13.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
|
@ -43,6 +43,7 @@ backtrace = "0.3.65"
|
||||
miette = { version = "4.7.1", features = ["fancy"] }
|
||||
thiserror = "1.0.31"
|
||||
toml = "0.5.8"
|
||||
base64 = "0.21"
|
||||
|
||||
# Well-known paths/dirs for e.g. cache
|
||||
dirs = "4.0.0"
|
||||
|
@ -25,3 +25,6 @@ See [INSTALL.md](INSTALL.md)
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md). Thanks!
|
||||
|
||||
# Funding
|
||||
This project was funded by the European Regional Development Fund (ERDF) in the context of the INTERFACER Project [https://www.interfacerproject.eu/](https://www.interfacerproject.eu/)
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 19f20f5154f0eced6288ff56cac840025ee51da1
|
||||
Subproject commit cde4677575f8e133ac764663e131c80fc891d545
|
@ -31,11 +31,18 @@ mod process;
|
||||
mod shelly;
|
||||
|
||||
pub trait Actor {
|
||||
/// The state is being restored after a restart or recreation of the actor
|
||||
fn restore(&mut self, state: ArchivedValue<State>) -> BoxFuture<'static, ()> {
|
||||
self.apply(state)
|
||||
}
|
||||
|
||||
/// The state is a changed state that is applied
|
||||
fn apply(&mut self, state: ArchivedValue<State>) -> BoxFuture<'static, ()>;
|
||||
}
|
||||
|
||||
pub struct ActorDriver<S: 'static> {
|
||||
signal: S,
|
||||
first: bool,
|
||||
|
||||
actor: Box<dyn Actor + Send + Sync>,
|
||||
future: Option<BoxFuture<'static, ()>>,
|
||||
@ -45,6 +52,7 @@ impl<S: Signal<Item = ArchivedValue<State>>> ActorDriver<S> {
|
||||
pub fn new(signal: S, actor: Box<dyn Actor + Send + Sync>) -> Self {
|
||||
Self {
|
||||
signal,
|
||||
first: true,
|
||||
actor,
|
||||
future: None,
|
||||
}
|
||||
@ -60,6 +68,7 @@ where
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
// Work until there is no more work to do.
|
||||
loop {
|
||||
tracing::trace!("polling actor driver");
|
||||
// Poll the `apply` future. And ensure it's completed before the next one is started
|
||||
match self
|
||||
.future
|
||||
@ -81,9 +90,15 @@ where
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(None) => return Poll::Ready(()),
|
||||
Poll::Ready(Some(state)) => {
|
||||
tracing::trace!(?state, "actor driver received state update");
|
||||
// This future MUST be polled before we exit from the Actor::poll because if we
|
||||
// do not do that it will not register the dependency and thus NOT BE POLLED.
|
||||
let f = self.actor.apply(state);
|
||||
let f = if self.first {
|
||||
self.first = false;
|
||||
self.actor.restore(state)
|
||||
} else {
|
||||
self.actor.apply(state)
|
||||
};
|
||||
self.future.replace(f);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use futures_util::future::BoxFuture;
|
||||
use std::collections::HashMap;
|
||||
use std::process::{Command, Stdio};
|
||||
use base64::Engine;
|
||||
|
||||
use crate::actors::Actor;
|
||||
use crate::db::ArchivedValue;
|
||||
@ -30,7 +31,7 @@ impl Process {
|
||||
}
|
||||
|
||||
impl Actor for Process {
|
||||
fn apply(&mut self, state: ArchivedValue<State>) -> BoxFuture<'static, ()> {
|
||||
fn restore(&mut self, state: ArchivedValue<State>) -> BoxFuture<'static, ()> {
|
||||
tracing::debug!(name=%self.name, cmd=%self.cmd, ?state,
|
||||
"Process actor updating state");
|
||||
let mut command = Command::new(&self.cmd);
|
||||
@ -60,6 +61,67 @@ impl Actor for Process {
|
||||
}
|
||||
}
|
||||
|
||||
let name = self.name.clone();
|
||||
Box::pin(async move {
|
||||
match command.output() {
|
||||
Ok(retv) if retv.status.success() => {
|
||||
tracing::trace!("Actor was restored");
|
||||
let outstr = String::from_utf8_lossy(&retv.stdout);
|
||||
for line in outstr.lines() {
|
||||
tracing::debug!(%name, %line, "actor stdout");
|
||||
}
|
||||
}
|
||||
Ok(retv) => {
|
||||
tracing::warn!(%name, ?state, code=?retv.status,
|
||||
"Actor failed to restore: nonzero exitcode"
|
||||
);
|
||||
if !retv.stderr.is_empty() {
|
||||
let errstr = String::from_utf8_lossy(&retv.stderr);
|
||||
for line in errstr.lines() {
|
||||
tracing::warn!(%name, %line, "actor stderr");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => tracing::warn!(%name, ?error, "process actor failed to run cmd"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn apply(&mut self, state: ArchivedValue<State>) -> BoxFuture<'static, ()> {
|
||||
tracing::debug!(name=%self.name, cmd=%self.cmd, ?state,
|
||||
"Process actor updating state");
|
||||
let mut command = Command::new(&self.cmd);
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.args(self.args.iter())
|
||||
.arg(&self.name);
|
||||
|
||||
if state.as_ref().raw.is_empty() {
|
||||
match &state.as_ref().inner.state {
|
||||
ArchivedStatus::Free => {
|
||||
command.arg("free");
|
||||
}
|
||||
ArchivedStatus::InUse(by) => {
|
||||
command.arg("inuse").arg(by.id.as_str());
|
||||
}
|
||||
ArchivedStatus::ToCheck(by) => {
|
||||
command.arg("tocheck").arg(by.id.as_str());
|
||||
}
|
||||
ArchivedStatus::Blocked(by) => {
|
||||
command.arg("blocked").arg(by.id.as_str());
|
||||
}
|
||||
ArchivedStatus::Disabled => {
|
||||
command.arg("disabled");
|
||||
}
|
||||
ArchivedStatus::Reserved(by) => {
|
||||
command.arg("reserved").arg(by.id.as_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let b64 = base64::prelude::BASE64_STANDARD_NO_PAD.encode(&state.as_ref().raw);
|
||||
command.arg("raw").arg(b64);
|
||||
}
|
||||
|
||||
let name = self.name.clone();
|
||||
Box::pin(async move {
|
||||
match command.output() {
|
||||
|
@ -6,10 +6,12 @@ use api::general_capnp::optional;
|
||||
use api::machine_capnp::machine::{
|
||||
self, admin, admin::Server as AdminServer, check, check::Server as CheckServer,
|
||||
in_use as inuse, in_use::Server as InUseServer, info, info::Server as InfoServer, manage,
|
||||
manage::Server as ManageServer, use_, use_::Server as UseServer, MachineState,
|
||||
manage::Server as ManageServer, use_, use_::Server as UseServer, MachineState, prodable::Server as ProdableServer,
|
||||
};
|
||||
use capnp::capability::Promise;
|
||||
use capnp::Error;
|
||||
use capnp_rpc::pry;
|
||||
use api::machine_capnp::machine::prodable::{ProdWithDataParams, ProdWithDataResults};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Machine {
|
||||
@ -52,6 +54,9 @@ impl Machine {
|
||||
}
|
||||
{
|
||||
builder.set_use(capnp_rpc::new_client(self.clone()));
|
||||
if self.resource.get_description().prodable {
|
||||
builder.set_prodable(capnp_rpc::new_client(self.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if self.session.has_manage(&self.resource) {
|
||||
@ -173,12 +178,20 @@ impl InUseServer for Machine {
|
||||
|
||||
fn send_raw_data(
|
||||
&mut self,
|
||||
_: inuse::SendRawDataParams,
|
||||
mut params: inuse::SendRawDataParams,
|
||||
_: inuse::SendRawDataResults,
|
||||
) -> Promise<(), ::capnp::Error> {
|
||||
Promise::err(::capnp::Error::unimplemented(
|
||||
"method not implemented".to_string(),
|
||||
))
|
||||
let data: Vec<u8> = pry!(pry!(params.get()).get_data()).to_vec();
|
||||
self.resource.send_raw(data);
|
||||
Promise::ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ProdableServer for Machine {
|
||||
fn prod_with_data(&mut self, mut params: ProdWithDataParams, _: ProdWithDataResults) -> Promise<(), Error> {
|
||||
let data: Vec<u8> = pry!(pry!(params.get()).get_data()).to_vec();
|
||||
self.resource.send_raw(data);
|
||||
Promise::ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,16 @@ pub struct MachineDescription {
|
||||
/// The permission required
|
||||
#[serde(flatten)]
|
||||
pub privs: PrivilegesBuf,
|
||||
|
||||
#[serde(default = "default_prodable", skip_serializing_if = "bool_is_false", deserialize_with = "deser_bool")]
|
||||
pub prodable: bool,
|
||||
}
|
||||
|
||||
fn default_prodable() -> bool {
|
||||
false
|
||||
}
|
||||
fn bool_is_false(b: &bool) -> bool {
|
||||
!b
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -116,6 +126,14 @@ pub struct ModuleConfig {
|
||||
pub params: HashMap<String, String>,
|
||||
}
|
||||
|
||||
fn deser_bool<'de, D>(d: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Ok(bool::deserialize(d).unwrap_or(false))
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn deser_option<'de, D, T>(d: D) -> std::result::Result<Option<T>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
|
@ -9,7 +9,7 @@ use crate::config::MachineDescription;
|
||||
use crate::db::ArchivedValue;
|
||||
use crate::resources::modules::fabaccess::{ArchivedStatus, MachineState, Status};
|
||||
use crate::resources::state::db::StateDB;
|
||||
use crate::resources::state::State;
|
||||
use crate::resources::state::{ArchivedState, State};
|
||||
use crate::session::SessionHandle;
|
||||
use crate::users::UserRef;
|
||||
use rkyv::option::ArchivedOption;
|
||||
@ -161,7 +161,7 @@ impl Resource {
|
||||
|
||||
fn set_state(&self, state: MachineState) {
|
||||
let mut serializer = AllocSerializer::<1024>::default();
|
||||
serializer.serialize_value(&state);
|
||||
let _ = serializer.serialize_value(&State::new(state));
|
||||
let archived = ArchivedValue::new(serializer.into_serializer().into_inner());
|
||||
self.inner.set_state(archived)
|
||||
}
|
||||
@ -177,6 +177,26 @@ impl Resource {
|
||||
self.set_state(new);
|
||||
}
|
||||
|
||||
pub fn send_raw(&self, data: Vec<u8>) {
|
||||
let mut serializer = AllocSerializer::<1024>::default();
|
||||
let archived = {
|
||||
let old_state_ref = self.inner.get_state_ref();
|
||||
let old_state: &ArchivedState = old_state_ref.as_ref();
|
||||
let m_state: MachineState =
|
||||
match Deserialize::<MachineState, _>::deserialize(&old_state.inner, &mut serializer) {
|
||||
Ok(s) => s,
|
||||
Err(error) => {
|
||||
tracing::error!(?error, "failed to deserialize stored state!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = serializer.serialize_value(&State::with_raw(m_state, data));
|
||||
ArchivedValue::new(serializer.into_serializer().into_inner())
|
||||
};
|
||||
self.inner.set_state(archived);
|
||||
}
|
||||
|
||||
pub async fn try_update(&self, session: SessionHandle, new: Status) {
|
||||
let old = self.get_state();
|
||||
let old: &Archived<State> = old.as_ref();
|
||||
|
@ -88,9 +88,7 @@ impl MachineState {
|
||||
Deserialize::deserialize(state, &mut Infallible).unwrap()
|
||||
}
|
||||
pub fn to_state(&self) -> State {
|
||||
State {
|
||||
inner: self.clone(),
|
||||
}
|
||||
State::new(self.clone())
|
||||
}
|
||||
|
||||
pub fn free(previous: Option<UserRef>) -> Self {
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::{fmt, hash::Hasher};
|
||||
use std::fmt;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
@ -20,6 +19,16 @@ pub mod value;
|
||||
#[archive_attr(derive(Debug))]
|
||||
pub struct State {
|
||||
pub inner: MachineState,
|
||||
pub raw: Vec<u8>,
|
||||
}
|
||||
impl State {
|
||||
pub fn new(inner: MachineState) -> Self {
|
||||
Self::with_raw(inner, Vec::new())
|
||||
}
|
||||
|
||||
pub fn with_raw(inner: MachineState, raw: Vec<u8>) -> Self {
|
||||
Self { inner, raw }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for State {
|
||||
@ -34,8 +43,8 @@ impl fmt::Debug for State {
|
||||
}
|
||||
|
||||
impl fmt::Display for ArchivedState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.inner, f)
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +71,7 @@ struct StateVisitor;
|
||||
impl<'de> serde::de::Visitor<'de> for StateVisitor {
|
||||
type Value = State;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "a map from OIDs to value objects")
|
||||
}
|
||||
|
||||
@ -75,7 +84,7 @@ impl<'de> serde::de::Visitor<'de> for StateVisitor {
|
||||
));
|
||||
}
|
||||
let val: MachineState = map.next_value()?;
|
||||
Ok(State { inner: val })
|
||||
Ok(State { inner: val, raw: Vec::new() })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +100,14 @@ def on_reserve(args, actor_name, user_id):
|
||||
print("process called with unexpected combo id %s and state 'Reserved'" % actor_name)
|
||||
exit(-1)
|
||||
|
||||
def on_raw(args, actor_name, data):
|
||||
"""
|
||||
Function called when a raw_write call was issued for the connected machine
|
||||
|
||||
data is the data passed with that call, base64-encoded
|
||||
"""
|
||||
if args.verbose > 2:
|
||||
print("on_raw called! data=" + data)
|
||||
|
||||
def main(args):
|
||||
"""
|
||||
@ -121,6 +129,9 @@ def main(args):
|
||||
else:
|
||||
args.verbose = 0
|
||||
|
||||
if args.restore and args.verbose > 0:
|
||||
print("running in restore mode")
|
||||
|
||||
# You could also check the actor name here and call different functions
|
||||
# depending on that variable instead of passing it to the state change
|
||||
# methods.
|
||||
@ -138,6 +149,8 @@ def main(args):
|
||||
on_disabled(args, args.name)
|
||||
elif new_state == "reserved":
|
||||
on_reserve(args, args.name, args.userid)
|
||||
elif new_state == "raw":
|
||||
on_raw(args, args.name, args.data)
|
||||
else:
|
||||
print("Process actor called with unknown state %s" % new_state)
|
||||
|
||||
@ -150,6 +163,7 @@ if __name__ == "__main__":
|
||||
|
||||
parser.add_argument("-q", "--quiet", help="be less verbose", action="store_true")
|
||||
parser.add_argument("-v", "--verbose", help="be more verbose", action="count")
|
||||
parser.add_argument("-r", "--restore", help="run in restore mode", action="store_true")
|
||||
|
||||
parser.add_argument("name",
|
||||
help="name of this actor as configured in bffh.dhall"
|
||||
@ -175,5 +189,8 @@ if __name__ == "__main__":
|
||||
parser_reserved = subparsers.add_parser("reserved")
|
||||
parser_reserved.add_argument("userid", help="The user that reserved the machine")
|
||||
|
||||
parser_raw = subparsers.add_parser("raw")
|
||||
parser_raw.add_argument("data", help="Raw data set for this actor")
|
||||
|
||||
args = parser.parse_args()
|
||||
main(args)
|
||||
|
@ -57,7 +57,7 @@
|
||||
--
|
||||
-- If you want either parents or permissions to be empty its best to completely skip it:
|
||||
testrole = {
|
||||
permissions = [ "lab.some.admin" ]
|
||||
permissions = [ "lab.some.admin", "bffh.users.admin", "bffh.users.manage" ]
|
||||
},
|
||||
somerole = {
|
||||
parents = ["testparent"],
|
||||
@ -135,7 +135,8 @@
|
||||
manage = "lab.test.admin",
|
||||
name = "Another",
|
||||
read = "lab.test.read",
|
||||
write = "lab.test.write"
|
||||
write = "lab.test.write",
|
||||
prodable = True
|
||||
},
|
||||
Yetmore = {
|
||||
description = "Yet more test machines",
|
||||
@ -213,7 +214,7 @@
|
||||
-- actor can only be connected to one machine.
|
||||
actor_connections = [
|
||||
{ machine = "Testmachine", actor = "Shelly1234" },
|
||||
{ machine = "Another", actor = "Bash" },
|
||||
{ machine = "Another", actor = "DoorControl1" },
|
||||
{ machine = "Yetmore", actor = "Bash2" },
|
||||
{ machine = "Yetmore", actor = "FailBash"}
|
||||
],
|
||||
@ -229,6 +230,9 @@
|
||||
-- Linking up machines to initiators. Similar to actors a machine can have several initiators assigned but an
|
||||
-- initiator can only be assigned to one machine.
|
||||
-- The below is once again how you have to define *no* initiators.
|
||||
init_connections = [] : List { machine : Text, initiator : Text }
|
||||
init_connections = [] : List { machine : Text, initiator : Text },
|
||||
--init_connections = [{ machine = "Testmachine", initiator = "Initiator" }]
|
||||
|
||||
spacename = Some "foospace",
|
||||
instanceurl = Some "https://example.com"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user