mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-11-10 17:43:23 +01:00
Lots of changes for better API stuffs
This commit is contained in:
parent
f8b9874f08
commit
e7bbc7e001
11
src/api.rs
11
src/api.rs
@ -7,6 +7,8 @@ use capnp::capability::{Params, Results, Promise};
|
||||
use crate::schema::connection_capnp;
|
||||
use crate::connection::Session;
|
||||
|
||||
use crate::db::Databases;
|
||||
|
||||
pub mod auth;
|
||||
mod machine;
|
||||
mod machines;
|
||||
@ -14,13 +16,14 @@ mod machines;
|
||||
use machines::Machines;
|
||||
|
||||
pub struct Bootstrap {
|
||||
session: Arc<Session>
|
||||
session: Arc<Session>,
|
||||
db: Databases,
|
||||
}
|
||||
|
||||
impl Bootstrap {
|
||||
pub fn new(session: Arc<Session>) -> Self {
|
||||
pub fn new(session: Arc<Session>, db: Databases) -> Self {
|
||||
info!(session.log, "Created Bootstrap");
|
||||
Self { session }
|
||||
Self { session, db }
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +56,7 @@ impl connection_capnp::bootstrap::Server for Bootstrap {
|
||||
) -> Promise<(), capnp::Error> {
|
||||
// TODO actual permission check and stuff
|
||||
if self.session.user.is_some() {
|
||||
let c = capnp_rpc::new_client(Machines::new(self.session.clone()));
|
||||
let c = capnp_rpc::new_client(Machines::new(self.session.clone(), self.db.clone()));
|
||||
res.get().set_machines(c);
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,27 @@
|
||||
use crate::schema::api_capnp::machine::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use capnp::capability::Promise;
|
||||
use capnp::Error;
|
||||
|
||||
use crate::schema::api_capnp::machine::*;
|
||||
use crate::db::machine::MachineIdentifier;
|
||||
use crate::connection::Session;
|
||||
use crate::db::Databases;
|
||||
|
||||
struct Machine;
|
||||
#[derive(Clone)]
|
||||
pub struct Machine {
|
||||
session: Arc<Session>,
|
||||
id: MachineIdentifier,
|
||||
db: Databases,
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub fn new() -> Self {
|
||||
Machine
|
||||
pub fn new(session: Arc<Session>, id: MachineIdentifier, db: Databases) -> Self {
|
||||
Machine { session, id, db }
|
||||
}
|
||||
}
|
||||
|
||||
struct Read;
|
||||
struct Read(Arc<Machine>);
|
||||
|
||||
impl read::Server for Read {
|
||||
fn info(&mut self,
|
||||
@ -24,7 +33,7 @@ impl read::Server for Read {
|
||||
}
|
||||
}
|
||||
|
||||
struct Write;
|
||||
struct Write(Arc<Machine>);
|
||||
|
||||
impl write::Server for Write {
|
||||
fn use_(&mut self,
|
||||
@ -36,7 +45,7 @@ impl write::Server for Write {
|
||||
}
|
||||
}
|
||||
|
||||
struct Manage;
|
||||
struct Manage(Arc<Machine>);
|
||||
|
||||
impl manage::Server for Manage {
|
||||
fn ok(&mut self,
|
||||
@ -48,7 +57,7 @@ impl manage::Server for Manage {
|
||||
}
|
||||
}
|
||||
|
||||
struct Admin;
|
||||
struct Admin(Arc<Machine>);
|
||||
|
||||
impl admin::Server for Admin {
|
||||
fn force_set_state(&mut self,
|
||||
|
@ -1,24 +1,30 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use slog::Logger;
|
||||
|
||||
use capnp::capability::Promise;
|
||||
use capnp::Error;
|
||||
|
||||
use crate::schema::api_capnp::machines;
|
||||
use crate::connection::Session;
|
||||
|
||||
use crate::db::Databases;
|
||||
use crate::db::machine::uuid_from_api;
|
||||
use crate::db::machine::MachineDB;
|
||||
|
||||
use super::machine::Machine;
|
||||
|
||||
/// An implementation of the `Machines` API
|
||||
pub struct Machines {
|
||||
/// A reference to the connection — as long as at least one API endpoint is
|
||||
/// still alive the session has to be as well.
|
||||
session: Arc<Session>,
|
||||
|
||||
db: Databases,
|
||||
}
|
||||
|
||||
impl Machines {
|
||||
pub fn new(session: Arc<Session>) -> Self {
|
||||
pub fn new(session: Arc<Session>, db: Databases) -> Self {
|
||||
info!(session.log, "Machines created");
|
||||
Self { session }
|
||||
Self { session, db }
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,10 +38,25 @@ impl machines::Server for Machines {
|
||||
}
|
||||
|
||||
fn get_machine(&mut self,
|
||||
_params: machines::GetMachineParams,
|
||||
params: machines::GetMachineParams,
|
||||
mut results: machines::GetMachineResults)
|
||||
-> Promise<(), Error>
|
||||
{
|
||||
Promise::ok(())
|
||||
match params.get() {
|
||||
Ok(reader) => {
|
||||
if let Ok(api_id) = reader.get_uuid() {
|
||||
let id = uuid_from_api(api_id);
|
||||
if self.db.machine.exists(id) {
|
||||
// TODO check disclose permission
|
||||
|
||||
let builder = results.get().init_machine();
|
||||
|
||||
let m = Machine::new(self.session.clone(), id, self.db.clone());
|
||||
}
|
||||
}
|
||||
Promise::ok(())
|
||||
}
|
||||
Err(e) => Promise::err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ pub fn read(path: &Path) -> Result<Settings> {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Settings {
|
||||
pub listens: Box<[Listen]>,
|
||||
pub shelly: Option<ShellyCfg>
|
||||
pub shelly: Option<ShellyCfg>,
|
||||
pub machines: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -53,6 +54,7 @@ impl Default for Settings {
|
||||
shelly: Some(ShellyCfg {
|
||||
mqtt_url: "127.0.0.1:1883".to_string()
|
||||
}),
|
||||
machines: PathBuf::from("/etc/bffh/machines/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ use capnp_rpc::{twoparty, rpc_twoparty_capnp};
|
||||
|
||||
use crate::schema::connection_capnp;
|
||||
|
||||
use crate::db::Databases;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Connection context
|
||||
// TODO this should track over several connections
|
||||
pub struct Session {
|
||||
@ -54,12 +57,12 @@ async fn handshake(log: &Logger, stream: &mut TcpStream) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_connection(log: Logger, stream: TcpStream) -> Result<()> {
|
||||
pub async fn handle_connection(log: Logger, stream: TcpStream, db: Databases) -> Result<()> {
|
||||
//handshake(&log, &mut stream).await?;
|
||||
|
||||
info!(log, "New connection from on {:?}", stream);
|
||||
let session = Arc::new(Session::new(log));
|
||||
let boots = Bootstrap::new(session);
|
||||
let boots = Bootstrap::new(session, db);
|
||||
let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots);
|
||||
|
||||
let network = twoparty::VatNetwork::new(stream.clone(), stream,
|
||||
|
@ -1,3 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
/// Access control storage
|
||||
///
|
||||
/// Stores&Retrieves Permissions and Roles
|
||||
@ -12,7 +15,8 @@ pub mod user;
|
||||
/// Stores&Retrieves Machines
|
||||
pub mod machine;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Databases {
|
||||
pub access: access::internal::Internal,
|
||||
pub machine: machine::internal::Internal,
|
||||
pub access: Arc<access::AccessControl>,
|
||||
pub machine: Arc<machine::MachineDB>,
|
||||
}
|
||||
|
@ -3,12 +3,12 @@
|
||||
|
||||
use std::fmt;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::HashMap;
|
||||
use std::cmp::Ordering;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use std::iter::FromIterator;
|
||||
use std::convert::{TryFrom, Into};
|
||||
|
||||
@ -32,6 +32,34 @@ pub mod internal;
|
||||
use crate::db::user::User;
|
||||
pub use internal::init;
|
||||
|
||||
pub struct AccessControl {
|
||||
sources: HashMap<String, Box<dyn RoleDB>>,
|
||||
}
|
||||
|
||||
impl AccessControl {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sources: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an access control source. If a source with the same name already existed it is
|
||||
/// replaced.
|
||||
pub fn add_source_unchecked(&mut self, name: String, source: Box<dyn RoleDB>) {
|
||||
self.sources.insert(name, source);
|
||||
}
|
||||
|
||||
pub async fn check<P: AsRef<Permission>>(&self, user: &User, perm: &P) -> Result<bool> {
|
||||
for v in self.sources.values() {
|
||||
if v.check(user, perm.as_ref())? {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RoleDB {
|
||||
fn get_role(&self, roleID: &RoleIdentifier) -> Result<Option<Role>>;
|
||||
|
||||
@ -39,7 +67,7 @@ pub trait RoleDB {
|
||||
///
|
||||
/// Default implementation which adapter may overwrite with more efficient specialized
|
||||
/// implementations.
|
||||
fn check<P: AsRef<Permission>>(&self, user: &User, perm: &P) -> Result<bool> {
|
||||
fn check(&self, user: &User, perm: &Permission) -> Result<bool> {
|
||||
self.check_roles(&user.roles, perm)
|
||||
}
|
||||
|
||||
@ -48,7 +76,7 @@ pub trait RoleDB {
|
||||
///
|
||||
/// A Default implementation exists which adapter may overwrite with more efficient specialized
|
||||
/// implementations.
|
||||
fn check_roles<P: AsRef<Permission>>(&self, roles: &[RoleIdentifier], perm: &P) -> Result<bool> {
|
||||
fn check_roles(&self, roles: &[RoleIdentifier], perm: &Permission) -> Result<bool> {
|
||||
// Tally all roles. Makes dependent roles easier
|
||||
let mut roleset = HashSet::new();
|
||||
for roleID in roles {
|
||||
@ -58,7 +86,7 @@ pub trait RoleDB {
|
||||
// Iter all unique role->permissions we've found and early return on match.
|
||||
for role in roleset.iter() {
|
||||
for perm_rule in role.permissions.iter() {
|
||||
if perm_rule.match_perm(perm) {
|
||||
if perm_rule.match_perm(&perm) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
@ -150,9 +150,9 @@ impl Internal {
|
||||
}
|
||||
|
||||
impl RoleDB for Internal {
|
||||
fn check<P: AsRef<Permission>>(&self, user: &User, perm: &P) -> Result<bool> {
|
||||
fn check(&self, user: &User, perm: &Permission) -> Result<bool> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
self._check(&txn, user, perm)
|
||||
self._check(&txn, user, &perm)
|
||||
}
|
||||
|
||||
fn get_role(&self, roleID: &RoleIdentifier) -> Result<Option<Role>> {
|
||||
|
@ -32,6 +32,8 @@ use futures_signals::signal::*;
|
||||
use crate::registries::StatusSignal;
|
||||
use crate::db::user::User;
|
||||
|
||||
use crate::machine::MachineDescription;
|
||||
|
||||
pub mod internal;
|
||||
use internal::Internal;
|
||||
|
||||
@ -54,13 +56,13 @@ pub enum Status {
|
||||
Reserved(UserIdentifier),
|
||||
}
|
||||
|
||||
fn uuid_from_api(uuid: crate::schema::api_capnp::u_u_i_d::Reader) -> Uuid {
|
||||
pub fn uuid_from_api(uuid: crate::schema::api_capnp::u_u_i_d::Reader) -> Uuid {
|
||||
let uuid0 = uuid.get_uuid0() as u128;
|
||||
let uuid1 = uuid.get_uuid1() as u128;
|
||||
let num: u128 = (uuid1 << 64) + uuid0;
|
||||
Uuid::from_u128(num)
|
||||
}
|
||||
fn api_from_uuid(uuid: Uuid, mut wr: crate::schema::api_capnp::u_u_i_d::Builder) {
|
||||
pub fn api_from_uuid(uuid: Uuid, mut wr: crate::schema::api_capnp::u_u_i_d::Builder) {
|
||||
let num = uuid.to_u128_le();
|
||||
let uuid0 = num as u64;
|
||||
let uuid1 = (num >> 64) as u64;
|
||||
@ -75,6 +77,7 @@ pub struct MachineState {
|
||||
}
|
||||
|
||||
pub fn init(log: Logger, config: &Settings, env: Arc<lmdb::Environment>) -> Result<Internal> {
|
||||
let mut machine_descriptions = MachineDescription::load_file(&config.machines)?;
|
||||
let mut flags = lmdb::DatabaseFlags::empty();
|
||||
flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true);
|
||||
let machdb = env.create_db(Some("machines"), flags)?;
|
||||
@ -82,3 +85,29 @@ pub fn init(log: Logger, config: &Settings, env: Arc<lmdb::Environment>) -> Resu
|
||||
|
||||
Ok(Internal::new(log, env, machdb))
|
||||
}
|
||||
|
||||
type MachMap = HashMap<MachineIdentifier, MachineDescription>;
|
||||
|
||||
pub struct MachineDB {
|
||||
state_db: Internal,
|
||||
def_db: MachMap,
|
||||
}
|
||||
|
||||
impl MachineDB {
|
||||
pub fn new(state_db: Internal, def_db: MachMap) -> Self {
|
||||
Self { state_db, def_db }
|
||||
}
|
||||
|
||||
pub fn exists(&self, id: MachineIdentifier) -> bool {
|
||||
self.def_db.get(&id).is_some()
|
||||
}
|
||||
|
||||
pub fn get_desc(&self, id: &MachineIdentifier) -> Option<&MachineDescription> {
|
||||
self.def_db.get(&id)
|
||||
}
|
||||
|
||||
pub fn get_state(&self, id: &MachineIdentifier) -> Option<MachineState> {
|
||||
// TODO: Error Handling
|
||||
self.state_db.get(id).unwrap_or(None)
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ use futures::future::Ready;
|
||||
use futures::stream::Iter;
|
||||
|
||||
use super::{MachineIdentifier, MachineState};
|
||||
use crate::machine::MachineDescription;
|
||||
use crate::error::Result;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -29,7 +30,7 @@ impl Internal {
|
||||
Self { log, env, db }
|
||||
}
|
||||
|
||||
pub fn get<T: Transaction>(&self, txn: &T, uuid: &Uuid)
|
||||
pub fn get_with_txn<T: Transaction>(&self, txn: &T, uuid: &Uuid)
|
||||
-> Result<Option<MachineState>>
|
||||
{
|
||||
match txn.get(self.db, uuid.as_bytes()) {
|
||||
@ -42,7 +43,12 @@ impl Internal {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put(&self, txn: &mut RwTransaction, uuid: &Uuid, status: MachineState)
|
||||
pub fn get(&self, id: &MachineIdentifier) -> Result<Option<MachineState>> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
self.get_with_txn(&txn, id)
|
||||
}
|
||||
|
||||
pub fn put_with_txn(&self, txn: &mut RwTransaction, uuid: &Uuid, status: MachineState)
|
||||
-> Result<()>
|
||||
{
|
||||
let bytes = flexbuffers::to_vec(status)?;
|
||||
@ -51,68 +57,6 @@ impl Internal {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_db(&mut self, txn: &mut RwTransaction, mut path: PathBuf) -> Result<()> {
|
||||
path.push("machines");
|
||||
for entry in std::fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
// will only ever be none if the path has no file name and then how is it a file?!
|
||||
let machID_str = path
|
||||
.file_stem().expect("Found a file with no filename?")
|
||||
.to_str().expect("Found an OsStr that isn't valid Unicode. Fix your OS!");
|
||||
let machID = match uuid::Uuid::from_str(machID_str) {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
warn!(self.log, "File {} had a invalid name. Expected an u64 in [0-9a-z] hex with optional file ending: {}. Skipping!", path.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let s = match fs::read_to_string(path.as_path()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!(self.log, "Failed to open file {}: {}, skipping!"
|
||||
, path.display()
|
||||
, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mach: MachineState = match toml::from_str(&s) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
warn!(self.log, "Failed to parse mach at path {}: {}, skipping!"
|
||||
, path.display()
|
||||
, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
self.put(txn, &machID, mach)?;
|
||||
debug!(self.log, "Loaded machine {}", machID);
|
||||
} else {
|
||||
warn!(self.log, "Path {} is not a file, skipping!", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dump_db<T: Transaction>(&self, txn: &T, mut path: PathBuf) -> Result<()> {
|
||||
path.push("machines");
|
||||
let mut mach_cursor = txn.open_ro_cursor(self.db)?;
|
||||
for buf in mach_cursor.iter_start() {
|
||||
let (kbuf, vbuf) = buf?;
|
||||
let machID = uuid::Uuid::from_slice(kbuf).unwrap();
|
||||
let mach: MachineState = flexbuffers::from_slice(vbuf)?;
|
||||
let filename = format!("{}.yml", machID.to_hyphenated().to_string());
|
||||
path.set_file_name(filename);
|
||||
let mut fp = std::fs::File::create(&path)?;
|
||||
let out = toml::to_vec(&mach)?;
|
||||
fp.write_all(&out)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn iter<T: Transaction>(&self, txn: &T) -> Result<impl Iterator<Item=MachineState>> {
|
||||
let mut cursor = txn.open_ro_cursor(self.db)?;
|
||||
Ok(cursor.iter_start().map(|buf| {
|
||||
|
@ -60,14 +60,14 @@ impl Machine {
|
||||
/// Requests to use a machine. Returns `true` if successful.
|
||||
///
|
||||
/// This will update the internal state of the machine, notifying connected actors, if any.
|
||||
pub fn request_use<P: access::RoleDB>
|
||||
pub async fn request_use
|
||||
( &mut self
|
||||
, pp: &P
|
||||
, access: access::AccessControl
|
||||
, who: &User
|
||||
) -> Result<bool>
|
||||
{
|
||||
// TODO: Check different levels
|
||||
if pp.check(who, &self.desc.privs.write)? {
|
||||
if access.check(who, &self.desc.privs.write).await? {
|
||||
self.state.set(MachineState { state: Status::InUse(who.id.clone()) });
|
||||
return Ok(true);
|
||||
} else {
|
||||
@ -97,7 +97,7 @@ pub struct MachineDescription {
|
||||
}
|
||||
|
||||
impl MachineDescription {
|
||||
fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<MachineIdentifier, MachineDescription>> {
|
||||
pub fn load_file<P: AsRef<Path>>(path: P) -> Result<HashMap<MachineIdentifier, MachineDescription>> {
|
||||
let content = fs::read(path)?;
|
||||
Ok(toml::from_slice(&content[..])?)
|
||||
}
|
||||
|
17
src/main.rs
17
src/main.rs
@ -112,7 +112,7 @@ fn main() -> Result<(), Error> {
|
||||
|
||||
|
||||
// If no `config` option is given use a preset default.
|
||||
let configpath = matches.value_of("config").unwrap_or("/etc/diflouroborane.toml");
|
||||
let configpath = matches.value_of("config").unwrap_or("/etc/bffh/config.toml");
|
||||
let config = config::read(&PathBuf::from_str(configpath).unwrap())?;
|
||||
|
||||
// Initialize the logging subsystem first to be able to better document the progress from now
|
||||
@ -153,7 +153,7 @@ fn main() -> Result<(), Error> {
|
||||
let mut txn = env.begin_rw_txn()?;
|
||||
let path = path.to_path_buf();
|
||||
pdb?.load_db(&mut txn, path.clone())?;
|
||||
mdb?.load_db(&mut txn, path)?;
|
||||
//mdb?.load_db(&mut txn, path)?;
|
||||
txn.commit()?;
|
||||
} else {
|
||||
error!(log, "You must provide a directory path to load from");
|
||||
@ -171,7 +171,7 @@ fn main() -> Result<(), Error> {
|
||||
let txn = env.begin_ro_txn()?;
|
||||
let path = path.to_path_buf();
|
||||
pdb?.dump_db(&txn, path.clone())?;
|
||||
mdb?.dump_db(&txn, path)?;
|
||||
//mdb?.dump_db(&txn, path)?;
|
||||
} else {
|
||||
error!(log, "You must provide a directory path to dump into");
|
||||
}
|
||||
@ -210,8 +210,15 @@ fn main() -> Result<(), Error> {
|
||||
|
||||
// Error out if any of the subsystems failed to start.
|
||||
let mdb = mdb?;
|
||||
let defs = machine::MachineDescription::load_file(&config.machines)?;
|
||||
let machdb = db::machine::MachineDB::new(mdb, defs);
|
||||
let pdb = pdb?;
|
||||
//let auth = auth?;
|
||||
let mut ac = db::access::AccessControl::new();
|
||||
ac.add_source_unchecked("Internal".to_string(), Box::new(pdb));
|
||||
let db = db::Databases {
|
||||
access: Arc::new(db::access::AccessControl::new()),
|
||||
machine: Arc::new(machdb),
|
||||
};
|
||||
|
||||
// Since the below closures will happen at a much later time we need to make sure all pointers
|
||||
// are still valid. Thus, Arc.
|
||||
@ -270,7 +277,7 @@ fn main() -> Result<(), Error> {
|
||||
let elog = log.clone();
|
||||
|
||||
// We handle the error using map_err
|
||||
let f = connection::handle_connection(log.clone(), socket)
|
||||
let f = connection::handle_connection(log.clone(), socket, db.clone())
|
||||
.map_err(move |e| {
|
||||
error!(log, "Error occured during protocol handling: {}", e);
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user