diff --git a/examples/bffh.dhall b/examples/bffh.dhall index c25633a..c0c26cf 100644 --- a/examples/bffh.dhall +++ b/examples/bffh.dhall @@ -39,4 +39,5 @@ } } , mqtt_url = "tcp://localhost:1883" +, db_path = "/tmp/bffh" } diff --git a/src/api.rs b/src/api.rs index c01c196..3cfb86f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -13,9 +13,11 @@ use crate::network::Network; pub mod auth; mod machine; mod machines; - use machines::Machines; +mod users; +use users::Users; + // TODO Session restoration by making the Bootstrap cap a SturdyRef pub struct Bootstrap { session: Arc, @@ -45,13 +47,6 @@ impl connection_capnp::bootstrap::Server for Bootstrap { Promise::ok(()) } - fn permission_system(&mut self, - _: PermissionSystemParams, - _: PermissionSystemResults - ) -> Promise<(), capnp::Error> { - Promise::ok(()) - } - fn machine_system(&mut self, _: MachineSystemParams, mut res: MachineSystemResults @@ -65,6 +60,11 @@ impl connection_capnp::bootstrap::Server for Bootstrap { let perms = accessdb.collect_permrules(&user.data) .map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e)))?; + debug!(session.log, "Giving MachineSystem cap to user {} with perms:", user.id); + for r in perms.iter() { + debug!(session.log, " {}", r); + } + // TODO actual permission check and stuff // Right now we only check that the user has authenticated at all. let c = capnp_rpc::new_client(Machines::new(user.id, perms, nw)); @@ -78,5 +78,31 @@ impl connection_capnp::bootstrap::Server for Bootstrap { Promise::from_future(f) } -} + fn user_system( + &mut self, + _: UserSystemParams, + mut results: UserSystemResults + ) -> Promise<(), capnp::Error> { + let session = self.session.clone(); + let accessdb = self.db.access.clone(); + let f = async move { + // Ensure the lock is dropped as soon as possible + if let Some(user) = { session.user.lock().await.clone() } { + let perms = accessdb.collect_permrules(&user.data) + .map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e)))?; + + // TODO actual permission check and stuff + // Right now we only check that the user has authenticated at all. + let c = capnp_rpc::new_client(Users::new(perms)); + results.get().set_user_system(c); + } + + // Promise is Ok either way, just the machine system may not be set, indicating as + // usual a lack of permission. + Ok(()) + }; + + Promise::from_future(f) + } +} diff --git a/src/api/users.rs b/src/api/users.rs new file mode 100644 index 0000000..c86e6c6 --- /dev/null +++ b/src/api/users.rs @@ -0,0 +1,43 @@ +use capnp::capability::Promise; + +use crate::db::access::{PermRule, Permission}; +use crate::schema::usersystem_capnp::user_system; +use crate::schema::usersystem_capnp::user_system::{info, manage}; + +#[derive(Clone, Debug)] +pub struct Users { + perms: Vec, +} + +impl Users { + pub fn new(perms: Vec) -> Self { + Self { perms } + } +} + +impl user_system::Server for Users { + fn info( + &mut self, + _: user_system::InfoParams, + _: user_system::InfoResults, + ) -> Promise<(), capnp::Error> { + Promise::ok(()) + } + + fn manage( + &mut self, + _: user_system::ManageParams, + mut results: user_system::ManageResults, + ) -> Promise<(), capnp::Error> { + let perm: &Permission = Permission::new("bffh.users.manage"); + if self.perms.iter().any(|rule| rule.match_perm(perm)) { + results.get().set_manage(capnp_rpc::new_client(self.clone())); + } + + Promise::ok(()) + } +} + +impl info::Server for Users {} + +impl manage::Server for Users {} diff --git a/src/config.rs b/src/config.rs index 7b9c060..d0f114c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ use std::default::Default; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::collections::HashMap; use serde::{Serialize, Deserialize}; @@ -36,6 +36,8 @@ pub struct Config { pub actor_connections: Box<[(String, String)]>, pub init_connections: Box<[(String, String)]>, + + pub db_path: PathBuf, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -83,9 +85,9 @@ impl Default for Config { port: Some(DEFAULT_PORT), } ]), - machines: machines, - actors: actors, - initiators: initiators, + machines, + actors, + initiators, mqtt_url: "tcp://localhost:1883".to_string(), actor_connections: Box::new([ ("Testmachine".to_string(), "Actor".to_string()), @@ -93,6 +95,8 @@ impl Default for Config { init_connections: Box::new([ ("Initiator".to_string(), "Testmachine".to_string()), ]), + + db_path: PathBuf::from("/run/bffh/database"), } } } diff --git a/src/db.rs b/src/db.rs index 5f688d5..77d85ca 100644 --- a/src/db.rs +++ b/src/db.rs @@ -42,7 +42,7 @@ impl Databases { let env = lmdb::Environment::new() .set_flags(lmdb::EnvironmentFlags::MAP_ASYNC | lmdb::EnvironmentFlags::NO_SUB_DIR) .set_max_dbs(LMDB_MAX_DB as libc::c_uint) - .open(&PathBuf::from_str("/tmp/a.db").unwrap())?; + .open(config.db_path.as_path())?; // Start loading the machine database, authentication system and permission system // All of those get a custom logger so the source of a log message can be better traced and diff --git a/src/db/access.rs b/src/db/access.rs index f8ba5b8..af5efc0 100644 --- a/src/db/access.rs +++ b/src/db/access.rs @@ -186,6 +186,31 @@ impl Role { } } +impl fmt::Display for Role { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "parents:")?; + if self.parents.is_empty() { + writeln!(f, " []")?; + } else { + writeln!(f, "")?; + for p in self.parents.iter() { + writeln!(f, " - {}", p)?; + } + } + write!(f, "permissions:")?; + if self.permissions.is_empty() { + writeln!(f, " []")?; + } else { + writeln!(f, "")?; + for p in self.permissions.iter() { + writeln!(f, " - {}", p)?; + } + } + + Ok(()) + } +} + type SourceID = String; fn split_once(s: &str, split: char) -> Option<(&str, &str)> { @@ -454,7 +479,7 @@ pub enum PermRule { impl PermRule { // Does this rule match that permission - pub fn match_perm>(&self, perm: &P) -> bool { + pub fn match_perm + ?Sized>(&self, perm: &P) -> bool { match self { PermRule::Base(ref base) => base.as_permission() == perm.as_ref(), PermRule::Children(ref parent) => parent.as_permission() > perm.as_ref() , diff --git a/src/db/access/internal.rs b/src/db/access/internal.rs index f149c3f..e6d399d 100644 --- a/src/db/access/internal.rs +++ b/src/db/access/internal.rs @@ -69,16 +69,15 @@ impl Internal { roles.insert(role_id.clone(), role); } } else { - info!(self.log, "Did not find role {} while trying to tally", role_id); + warn!(self.log, "Did not find role {} while trying to tally", role_id); } Ok(()) } pub fn _get_role<'txn, T: Transaction>(&self, txn: &'txn T, role_id: &RoleIdentifier) -> Result> { - let string = format!("{}", role_id); - debug!(self.log, "Reading role '{}'", &string); - match txn.get(self.roledb, &string.as_bytes()) { + debug!(self.log, "Reading role '{}'", role_id.name); + match txn.get(self.roledb, &role_id.name.as_bytes()) { Ok(bytes) => { Ok(Some(flexbuffers::from_slice(bytes)?)) }, @@ -89,8 +88,7 @@ impl Internal { fn put_role(&self, txn: &mut RwTransaction, role_id: &RoleIdentifier, role: Role) -> Result<()> { let bytes = flexbuffers::to_vec(role)?; - let string = format!("{}", role_id); - txn.put(self.roledb, &string.as_bytes(), &bytes, lmdb::WriteFlags::empty())?; + txn.put(self.roledb, &role_id.name.as_bytes(), &bytes, lmdb::WriteFlags::empty())?; Ok(()) } @@ -107,8 +105,8 @@ impl Internal { for r in cursor.iter_start() { match r { Ok( (k,v) ) => { - let role_id_str = unsafe { std::str::from_utf8_unchecked(k) }; - let role_id = role_id_str.parse::().unwrap(); + let role_name_str = unsafe { std::str::from_utf8_unchecked(k) }; + let role_id = RoleIdentifier::local_from_str(role_name_str.to_string(), "lmdb".to_string()); match flexbuffers::from_slice(v) { Ok(role) => vec.push((role_id, role)), Err(e) => error!(self.log, "Bad format for roleid {}: {}", role_id, e), @@ -126,7 +124,7 @@ impl Internal { self.load_roles_txn(&mut txn, path.as_ref())?; // In case the above didn't error, commit. - txn.commit(); + txn.commit()?; Ok(()) } fn load_roles_txn(&self, txn: &mut RwTransaction, path: &Path) -> Result<()> { diff --git a/src/main.rs b/src/main.rs index 71c0e68..528f2b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,8 +141,10 @@ fn maybe(matches: clap::ArgMatches, log: Arc) -> Result<(), Error> { if matches.is_present("dump") { let db = db::Databases::new(&log, &config)?; - let v = db.access.dump_roles(); - info!(log, "Roles {:?}", v); + let v = db.access.dump_roles().unwrap(); + for (id, role) in v.iter() { + info!(log, "Role {}:\n{}", id, role); + } Ok(()) } else if matches.is_present("load") { let db = db::Databases::new(&log, &config)?;