From e7bbc7e001f9657c761118ee490ef7929456057e Mon Sep 17 00:00:00 2001 From: Gregor Reitzenstein Date: Fri, 20 Nov 2020 13:06:55 +0100 Subject: [PATCH] Lots of changes for better API stuffs --- src/api.rs | 11 +++--- src/api/machine.rs | 25 ++++++++----- src/api/machines.rs | 33 +++++++++++++---- src/config.rs | 4 ++- src/connection.rs | 7 ++-- src/db.rs | 8 +++-- src/db/access.rs | 36 ++++++++++++++++--- src/db/access/internal.rs | 4 +-- src/db/machine.rs | 33 +++++++++++++++-- src/db/machine/internal.rs | 72 +++++--------------------------------- src/machine.rs | 8 ++--- src/main.rs | 17 ++++++--- 12 files changed, 154 insertions(+), 104 deletions(-) diff --git a/src/api.rs b/src/api.rs index 8e1d85b..9655cc8 100644 --- a/src/api.rs +++ b/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: Arc, + db: Databases, } impl Bootstrap { - pub fn new(session: Arc) -> Self { + pub fn new(session: Arc, 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); } diff --git a/src/api/machine.rs b/src/api/machine.rs index 88f467b..c41208a 100644 --- a/src/api/machine.rs +++ b/src/api/machine.rs @@ -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, + id: MachineIdentifier, + db: Databases, +} impl Machine { - pub fn new() -> Self { - Machine + pub fn new(session: Arc, id: MachineIdentifier, db: Databases) -> Self { + Machine { session, id, db } } } -struct Read; +struct Read(Arc); impl read::Server for Read { fn info(&mut self, @@ -24,7 +33,7 @@ impl read::Server for Read { } } -struct Write; +struct Write(Arc); impl write::Server for Write { fn use_(&mut self, @@ -36,7 +45,7 @@ impl write::Server for Write { } } -struct Manage; +struct Manage(Arc); impl manage::Server for Manage { fn ok(&mut self, @@ -48,7 +57,7 @@ impl manage::Server for Manage { } } -struct Admin; +struct Admin(Arc); impl admin::Server for Admin { fn force_set_state(&mut self, diff --git a/src/api/machines.rs b/src/api/machines.rs index edf7809..dd325e5 100644 --- a/src/api/machines.rs +++ b/src/api/machines.rs @@ -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, + + db: Databases, } impl Machines { - pub fn new(session: Arc) -> Self { + pub fn new(session: Arc, 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), + } } } diff --git a/src/config.rs b/src/config.rs index d3c1a52..c56ea26 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,7 +25,8 @@ pub fn read(path: &Path) -> Result { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Settings { pub listens: Box<[Listen]>, - pub shelly: Option + pub shelly: Option, + 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/") } } } diff --git a/src/connection.rs b/src/connection.rs index 2f878c4..78575b1 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -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, diff --git a/src/db.rs b/src/db.rs index 5125623..6a67f44 100644 --- a/src/db.rs +++ b/src/db.rs @@ -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, + pub machine: Arc, } diff --git a/src/db/access.rs b/src/db/access.rs index 1612417..dc77cf3 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -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>, +} + +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) { + self.sources.insert(name, source); + } + + pub async fn check>(&self, user: &User, perm: &P) -> Result { + 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>; @@ -39,7 +67,7 @@ pub trait RoleDB { /// /// Default implementation which adapter may overwrite with more efficient specialized /// implementations. - fn check>(&self, user: &User, perm: &P) -> Result { + fn check(&self, user: &User, perm: &Permission) -> Result { 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>(&self, roles: &[RoleIdentifier], perm: &P) -> Result { + fn check_roles(&self, roles: &[RoleIdentifier], perm: &Permission) -> Result { // 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); } } diff --git a/src/db/access/internal.rs b/src/db/access/internal.rs index 41ffe3c..05d73e6 100644 --- a/src/db/access/internal.rs +++ b/src/db/access/internal.rs @@ -150,9 +150,9 @@ impl Internal { } impl RoleDB for Internal { - fn check>(&self, user: &User, perm: &P) -> Result { + fn check(&self, user: &User, perm: &Permission) -> Result { let txn = self.env.begin_ro_txn()?; - self._check(&txn, user, perm) + self._check(&txn, user, &perm) } fn get_role(&self, roleID: &RoleIdentifier) -> Result> { diff --git a/src/db/machine.rs b/src/db/machine.rs index 5b2d84c..83b6b99 100644 --- a/src/db/machine.rs +++ b/src/db/machine.rs @@ -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) -> Result { + 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) -> Resu Ok(Internal::new(log, env, machdb)) } + +type MachMap = HashMap; + +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 { + // TODO: Error Handling + self.state_db.get(id).unwrap_or(None) + } +} diff --git a/src/db/machine/internal.rs b/src/db/machine/internal.rs index 98629c2..8b6f90b 100644 --- a/src/db/machine/internal.rs +++ b/src/db/machine/internal.rs @@ -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(&self, txn: &T, uuid: &Uuid) + pub fn get_with_txn(&self, txn: &T, uuid: &Uuid) -> Result> { 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> { + 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(&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(&self, txn: &T) -> Result> { let mut cursor = txn.open_ro_cursor(self.db)?; Ok(cursor.iter_start().map(|buf| { diff --git a/src/machine.rs b/src/machine.rs index 77fa7c1..bb86055 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -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 + pub async fn request_use ( &mut self - , pp: &P + , access: access::AccessControl , who: &User ) -> Result { // 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>(path: P) -> Result> { + pub fn load_file>(path: P) -> Result> { let content = fs::read(path)?; Ok(toml::from_slice(&content[..])?) } diff --git a/src/main.rs b/src/main.rs index 96be7be..0bf8573 100644 --- a/src/main.rs +++ b/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); })