From 9e244aab7e2d7e165e0b1e4e276ab15c7674132c Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Wed, 20 Oct 2021 18:37:50 +0200 Subject: [PATCH] All the things. --- Cargo.lock | 42 ++++++++ Cargo.toml | 5 +- examples/bffh.dhall | 2 +- src/{db => }/access/internal.rs | 0 src/bin/bffhd.rs | 115 ++++++++++---------- src/config.rs | 4 +- src/db/fix.rs | 13 +-- src/db/machine.rs | 83 --------------- src/db/machine/internal.rs | 62 ----------- src/db/mod.rs | 76 +++++++++++++- src/db/pass.rs | 98 +++++++---------- src/db/resources.rs | 27 ++++- src/db/state.rs | 9 ++ src/db/typed.rs | 11 +- src/db/user.rs | 181 ++++++++++---------------------- src/db/user/internal.rs | 70 ------------ src/error.rs | 13 +-- src/lib.rs | 2 +- src/permissions.rs | 8 +- 19 files changed, 332 insertions(+), 489 deletions(-) rename src/{db => }/access/internal.rs (100%) delete mode 100644 src/db/machine.rs delete mode 100644 src/db/machine/internal.rs delete mode 100644 src/db/user/internal.rs diff --git a/Cargo.lock b/Cargo.lock index f2c125c..04d9a37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "arrayvec" version = "0.5.2" @@ -208,6 +214,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bincode" version = "2.0.0-dev" @@ -247,6 +259,17 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.7.3" @@ -450,6 +473,12 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -550,6 +579,7 @@ dependencies = [ "rkyv_dyn", "rkyv_typename", "rsasl", + "rust-argon2", "serde", "serde_dhall", "serde_json", @@ -1641,6 +1671,18 @@ dependencies = [ "libc", ] +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3d3c01d..91f050f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,8 +70,8 @@ async-trait = "0.1.51" lazy_static = "1.4.0" -#rust-argon2 = "0.8.3" -#rand = "0.8.4" +rust-argon2 = "0.8.3" +rand = "0.8.4" [build-dependencies] capnpc = "0.14.4" @@ -82,7 +82,6 @@ walkdir = "2.3.2" futures-test = "0.3.16" tempfile = "3.2" bincode = "2.0.0-dev" -rand = "0.8" [patch.crates-io] bincode = { git = "https://github.com/dequbed/bincode.git", branch = "feature/in_place_buffer" } diff --git a/examples/bffh.dhall b/examples/bffh.dhall index 2c90eed..605405c 100644 --- a/examples/bffh.dhall +++ b/examples/bffh.dhall @@ -18,7 +18,7 @@ , "notahostandnoport" ] , machines = ./machines.dhall -, db_path = "/tmp/bffh" +, db_path = "/tmp/bffh/" , roles = ./roles.dhall , mqtt_url = "tcp://localhost:1883" } diff --git a/src/db/access/internal.rs b/src/access/internal.rs similarity index 100% rename from src/db/access/internal.rs rename to src/access/internal.rs diff --git a/src/bin/bffhd.rs b/src/bin/bffhd.rs index 7235523..3398125 100644 --- a/src/bin/bffhd.rs +++ b/src/bin/bffhd.rs @@ -6,6 +6,7 @@ use std::{ use clap::{App, Arg, crate_version, crate_description, crate_name}; use std::str::FromStr; use diflouroborane::{config, error::Error}; +use diflouroborane::db::{Databases, Dump}; use std::net::ToSocketAddrs; fn main_res() -> Result<(), Error> { @@ -88,66 +89,66 @@ fn main_res() -> Result<(), Error> { println!("Final listens: {:?}", sockaddrs); - /* + let dbs = Databases::create(config.db_path)?; + if matches.is_present("dump") { - let db = db::Databases::new(&log, &config)?; - let v = db.access.dump_roles().unwrap(); - for (id, role) in v.iter() { - tracing::info!("Role {}:\n{}", id, role); - } + let dump = Dump::new(&dbs)?; + let encoded = serde_json::to_vec(&dump).unwrap(); - let v = db.userdb.list_users()?; - for user in v.iter() { - tracing::info!("User {}:\n{:?}", user.id, user.data); - } - Ok(()) - } else if matches.is_present("load") { - let db = db::Databases::new(&log, &config)?; - let mut dir = PathBuf::from(matches.value_of_os("load").unwrap()); - - dir.push("users.toml"); - let map = db::user::load_file(&dir)?; - for (uid,user) in map.iter() { - db.userdb.put_user(uid, user)?; - } - tracing::debug!("Loaded users: {:?}", map); - dir.pop(); - - Ok(()) - } else { - let ex = smol::Executor::new(); - let db = db::Databases::new(&log, &config)?; - - let machines = machine::load(&config)?; - let (actor_map, actors) = actor::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); - - for (a,b) in config.actor_connections.iter() { - if let Err(e) = network.connect_actor(a,b) { - tracing::error!("{}", e); - } - tracing::info!("[Actor] Connected {} to {}", a, b); - } - - for (a,b) in config.init_connections.iter() { - if let Err(e) = network.connect_init(a,b) { - tracing::error!("{}", e); - } - tracing::info!("[Initi] Connected {} to {}", a, b); - } - - for actor in actors.into_iter() { - ex.spawn(actor).detach(); - } - for init in initiators.into_iter() { - ex.spawn(init).detach(); - } - - server::serve_api_connections(log.clone(), config, db, network, ex) + // Direct writing to fd 1 is faster but also prevents any print-formatting that could + // invalidate the generated TOML + let stdout = io::stdout(); + let mut handle = stdout.lock(); + handle.write_all(&encoded).unwrap(); } - */ + /* + } else if matches.is_present("load") { + let db = db::Databases::new(&log, &config)?; + let mut dir = PathBuf::from(matches.value_of_os("load").unwrap()); + + dir.push("users.toml"); + let map = db::user::load_file(&dir)?; + for (uid,user) in map.iter() { + db.userdb.put_user(uid, user)?; + } + tracing::debug!("Loaded users: {:?}", map); + dir.pop(); + + Ok(()) + } else { + let ex = smol::Executor::new(); + let db = db::Databases::new(&log, &config)?; + + let machines = machine::load(&config)?; + let (actor_map, actors) = actor::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); + + for (a,b) in config.actor_connections.iter() { + if let Err(e) = network.connect_actor(a,b) { + tracing::error!("{}", e); + } + tracing::info!("[Actor] Connected {} to {}", a, b); + } + + for (a,b) in config.init_connections.iter() { + if let Err(e) = network.connect_init(a,b) { + tracing::error!("{}", e); + } + tracing::info!("[Initi] Connected {} to {}", a, b); + } + + for actor in actors.into_iter() { + ex.spawn(actor).detach(); + } + for init in initiators.into_iter() { + ex.spawn(init).detach(); + } + + server::serve_api_connections(log.clone(), config, db, network, ex) + } + */ Ok(()) } diff --git a/src/config.rs b/src/config.rs index 73299cd..e90cce6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -106,8 +106,8 @@ impl<'de> serde::de::Visitor<'de> for ListenVisitor { type Value = Listen; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "A string encoding a valid IP or Hostname (e.g. 127.0.0.1 or [::1]) with\ - or without a defined port") + write!(formatter, "A string encoding a valid IP or Hostname (e.g. 127.0.0.1 or [::1]) with \ + or without a defined port") } fn visit_str(self, v: &str) -> std::result::Result diff --git a/src/db/fix.rs b/src/db/fix.rs index ce5be19..329e90a 100644 --- a/src/db/fix.rs +++ b/src/db/fix.rs @@ -3,7 +3,7 @@ use std::{ ops::Deref, }; -use lmdb::Transaction; +use crate::db::Transaction; /// Memory Fixpoint for a value in the DB /// @@ -22,11 +22,12 @@ pub struct LMDBorrow { impl<'env, T, V> LMDBorrow where T: Transaction, { - pub unsafe fn reborrow(ptr: &'_ V) -> NonNull { - ptr.into() - } pub unsafe fn new(ptr: NonNull, txn: T) -> Self { - Self { ptr: ptr.into(), txn, } + Self { ptr: ptr.into(), txn } + } + + pub fn unwrap_txn(self) -> T { + self.txn } } @@ -39,4 +40,4 @@ impl<'env, T, V> Deref for LMDBorrow // valid pointer so this is safe. unsafe { self.ptr.as_ref() } } -} +} \ No newline at end of file diff --git a/src/db/machine.rs b/src/db/machine.rs deleted file mode 100644 index 7e371c4..0000000 --- a/src/db/machine.rs +++ /dev/null @@ -1,83 +0,0 @@ -use slog::Logger; - -use serde::{Serialize, Deserialize}; - -use std::sync::Arc; - -use crate::error::Result; -use crate::config::Config; - -use crate::db::user::UserId; - -pub mod internal; -use internal::Internal; - -pub type MachineIdentifier = String; -pub type Priority = u64; - -/// Status of a Machine -#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub enum Status { - /// Not currently used by anybody - Free, - /// Used by somebody - InUse(Option), - /// Was used by somebody and now needs to be checked for cleanliness - ToCheck(UserId), - /// Not used by anybody but also can not be used. E.g. down for maintenance - Blocked(UserId), - /// Disabled for some other reason - Disabled, - /// Reserved - Reserved(UserId), -} - - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -/// The status of the machine -pub struct MachineState { - pub state: Status, -} - -impl MachineState { - pub fn new() -> Self { - Self { state: Status::Free } - } - - pub fn from(state: Status) -> Self { - Self { state } - } - - pub fn free() -> Self { - Self { state: Status::Free } - } - - pub fn used(uid: Option) -> Self { - Self { state: Status::InUse(uid) } - } - - pub fn blocked(uid: UserId) -> Self { - Self { state: Status::Blocked(uid) } - } - - pub fn disabled() -> Self { - Self { state: Status::Disabled } - } - - pub fn reserved(uid: UserId) -> Self { - Self { state: Status::Reserved(uid) } - } - - pub fn check(uid: UserId) -> Self { - Self { state: Status::ToCheck(uid) } - } -} - -pub fn init(log: Logger, _config: &Config, env: Arc) -> Result { - let mut flags = lmdb::DatabaseFlags::empty(); - //flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); - let machdb = env.create_db(Some("machines"), flags)?; - debug!(&log, "Opened machine db successfully."); - - Ok(Internal::new(log, env, machdb)) -} diff --git a/src/db/machine/internal.rs b/src/db/machine/internal.rs deleted file mode 100644 index 9ae80be..0000000 --- a/src/db/machine/internal.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::sync::Arc; - -use slog::Logger; - -use lmdb::{Environment, Transaction, RwTransaction, Cursor}; - -use super::{MachineIdentifier, MachineState}; -use crate::error::Result; - -#[derive(Clone, Debug)] -pub struct Internal { - log: Logger, - env: Arc, - db: lmdb::Database, -} - -impl Internal { - pub fn new(log: Logger, env: Arc, db: lmdb::Database) -> Self { - Self { log, env, db } - } - - pub fn get_with_txn(&self, txn: &T, id: &String) - -> Result> - { - match txn.get(self.db, &id.as_bytes()) { - Ok(bytes) => { - let machine: MachineState = flexbuffers::from_slice(bytes)?; - Ok(Some(machine)) - }, - Err(lmdb::Error::NotFound) => { Ok(None) }, - Err(e) => { Err(e.into()) }, - } - } - - 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: &String, status: &MachineState) - -> Result<()> - { - let bytes = flexbuffers::to_vec(status)?; - txn.put(self.db, &uuid.as_bytes(), &bytes, lmdb::WriteFlags::empty())?; - - Ok(()) - } - - pub fn put(&self, id: &MachineIdentifier, status: &MachineState) -> Result<()> { - let mut txn = self.env.begin_rw_txn()?; - self.put_with_txn(&mut txn, id, status)?; - txn.commit().map_err(Into::into) - } - - pub fn iter(&self, txn: &T) -> Result> { - let mut cursor = txn.open_ro_cursor(self.db)?; - Ok(cursor.iter_start().map(|buf| { - let (_kbuf, vbuf) = buf.unwrap(); - flexbuffers::from_slice(vbuf).unwrap() - })) - } -} diff --git a/src/db/mod.rs b/src/db/mod.rs index 4e8984d..5e69c79 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -23,7 +23,7 @@ mod typed; // re-exports pub use typed::{ DB, - Cursor, + TypedCursor, Adapter, OutputBuffer, @@ -49,8 +49,25 @@ pub use resources::{ ResourceDB, }; +mod pass; +pub use pass::{ + PassDB, +}; + +mod user; +pub use user::{ + UserDB, +}; + use lmdb::Error; use rkyv::ser::serializers::AlignedSerializer; +use std::sync::Arc; +use std::path::Path; +use crate::db::user::User; +use crate::db::resources::Resource; +use std::collections::HashMap; +use crate::state::State; +use std::iter::FromIterator; #[derive(Debug)] pub enum DBError { @@ -58,6 +75,8 @@ pub enum DBError { RKYV( as Fallible>::Error), } +pub(crate) type Result = std::result::Result; + impl From for DBError { fn from(e: lmdb::Error) -> Self { Self::LMDB(e) @@ -112,4 +131,59 @@ impl>> Adapter for AlignedAdapter fn from_db_err(e: Error) -> ::Error { e } +} + +pub struct Databases { + pub userdb: UserDB, + pub passdb: PassDB, + pub resourcedb: ResourceDB, + pub statedb: StateDB, +} + +impl Databases { + pub fn open>(path: P) -> Result { + let env = Arc::new(Environment::new() + .open(&Path::join(path.as_ref(), "internal"))? + ); + let userdb = unsafe { UserDB::open(env.clone())? }; + let passdb = unsafe { PassDB::open(env.clone())? }; + let resourcedb = unsafe { ResourceDB::open(env)? }; + + let statedb = StateDB::open(&Path::join(path.as_ref(), "state"))?; + + Ok(Self { userdb, passdb, resourcedb, statedb }) + } + + pub fn create>(path: P) -> Result { + let env = Arc::new(Environment::new() + .set_max_dbs(16) + .open(path.as_ref())? + ); + let userdb = unsafe { UserDB::create(env.clone())? }; + let passdb = unsafe { PassDB::create(env.clone())? }; + let resourcedb = unsafe { ResourceDB::create(env)? }; + + let statedb = StateDB::create(&Path::join(path.as_ref(), "state"))?; + + Ok(Self { userdb, passdb, resourcedb, statedb }) + } +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct Dump { + users: HashMap, + passwds: HashMap, + resources: HashMap, + states: HashMap, +} + +impl Dump { + pub fn new(dbs: &Databases) -> Result { + let users = HashMap::from_iter(dbs.userdb.get_all()?.into_iter()); + let passwds = HashMap::new(); + let resources = HashMap::new(); + let states = HashMap::new(); + + Ok(Self { users, passwds, resources, states }) + } } \ No newline at end of file diff --git a/src/db/pass.rs b/src/db/pass.rs index 9b3c415..6b25427 100644 --- a/src/db/pass.rs +++ b/src/db/pass.rs @@ -1,78 +1,58 @@ use std::sync::Arc; -use std::path::Path; -use std::fs; -use std::collections::HashMap; +use super::Environment; +use super::AllocAdapter; +use super::DB; +use super::raw::RawDB; +use super::{DatabaseFlags, WriteFlags}; +use crate::db::Result; +use super::Transaction; use argon2; -use lmdb::{Environment, Transaction, RwTransaction}; -use slog::Logger; - -use crate::error::Result; +type Adapter = AllocAdapter; +#[derive(Clone)] pub struct PassDB { - log: Logger, env: Arc, - db: lmdb::Database, + db: DB, } impl PassDB { - pub fn new(log: Logger, env: Arc, db: lmdb::Database) -> Self { - Self { log, env, db } + pub unsafe fn new(env: Arc, db: RawDB) -> Self { + let db = DB::new_unchecked(db); + Self { env, db } } - pub fn init(log: Logger, env: Arc) -> Result { - let mut flags = lmdb::DatabaseFlags::empty(); - flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); - let db = env.create_db(Some("pass"), flags)?; - - Ok(Self::new(log, env, db)) + pub unsafe fn open(env: Arc) -> Result { + let db = RawDB::open(&env, Some("pass"))?; + Ok(Self::new(env, db)) } - /// Check a password for a given authcid. - /// - /// `Ok(None)` means the given authcid is not stored in the database - pub fn check_with_txn(&self, txn: &T, authcid: &str, password: &[u8]) -> Result> { - match txn.get(self.db, &authcid.as_bytes()) { - Ok(bytes) => { - let encoded = unsafe { std::str::from_utf8_unchecked(bytes) }; - let res = argon2::verify_encoded(encoded, password)?; - Ok(Some(res)) - }, - Err(lmdb::Error::NotFound) => { Ok(None) }, - Err(e) => { Err(e.into()) }, - } + pub unsafe fn create(env: Arc) -> Result { + let flags = DatabaseFlags::empty(); + let db = RawDB::create(&env, Some("pass"), flags)?; + Ok(Self::new(env, db)) } - pub fn check(&self, authcid: &str, password: &[u8]) -> Result> { + + pub fn check_pw>(&self, uid: &str, inpass: P) -> Result> { let txn = self.env.begin_ro_txn()?; - self.check_with_txn(&txn, authcid, password) - } - - /// Store a password for a given authcid, potentially overwriting an existing password - pub fn store_with_txn(&self, txn: &mut RwTransaction, authcid: &str, password: &[u8]) -> Result<()> { - let config = argon2::Config::default(); - let salt: [u8; 16] = rand::random(); - let hash = argon2::hash_encoded(password, &salt, &config)?; - txn.put(self.db, &authcid.as_bytes(), &hash.as_bytes(), lmdb::WriteFlags::empty()) - .map_err(Into::into) - } - - pub fn insert_multiple(&self, vec: Vec<(String, String)>) -> Result<()> { - let mut txn = self.env.begin_rw_txn()?; - for (authcid, password) in vec.iter() { - self.store_with_txn(&mut txn, authcid.as_ref(), password.as_bytes())?; + if let Some(pass) = self.db.get(&txn, &uid.as_bytes())? { + Ok(argon2::verify_encoded(pass.as_str(), inpass.as_ref()) + .ok()) + } else { + Ok(None) } + } + + pub fn set_password>(&self, uid: &str, password: P) -> Result<()> { + let cfg = argon2::Config::default(); + let salt: [u8; 10] = rand::random(); + let enc = argon2::hash_encoded(password.as_ref(), &salt, &cfg) + .expect("Hashing password failed for static valid config"); + + let flags = WriteFlags::empty(); + let mut txn = self.env.begin_rw_txn()?; + self.db.put(&mut txn, &uid.as_bytes(), &enc, flags)?; txn.commit()?; - - let v: Vec<&String> = vec.iter().map(|(a,_)| a).collect(); - debug!(self.log, "Loaded passwords for: {:?}", v); - Ok(()) } - - pub fn load_file>(&self, path: P) -> Result<()> { - let f = fs::read(path)?; - let mut map: HashMap = toml::from_slice(&f)?; - - self.insert_multiple(map.drain().collect()) - } -} +} \ No newline at end of file diff --git a/src/db/resources.rs b/src/db/resources.rs index a11b74d..0fdc5de 100644 --- a/src/db/resources.rs +++ b/src/db/resources.rs @@ -7,10 +7,12 @@ use super::{ use crate::db::AlignedAdapter; use crate::db::raw::RawDB; use std::sync::Arc; -use lmdb::Environment; -use crate::db; +use crate::db::{Environment, DatabaseFlags}; +use crate::db::Result; +#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Archive, Serialize, Deserialize)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct Resource { uuid: u128, id: String, @@ -18,6 +20,7 @@ pub struct Resource { description_idx: u64, } +#[derive(Clone)] pub struct ResourceDB { env: Arc, db: DB>, @@ -32,10 +35,24 @@ impl ResourceDB { Self { env, db, id_index } } - pub fn lookup_id>(&self, id: S) -> Result, db::Error> { + pub unsafe fn open(env: Arc) -> Result { + let db = RawDB::open(&env, Some("resources"))?; + let idx = RawDB::open(&env, Some("resources-idx"))?; + Ok(Self::new(env, db, idx)) + } + + pub unsafe fn create(env: Arc) -> Result { + let flags = DatabaseFlags::empty(); + let db = RawDB::create(&env, Some("resources"), flags)?; + let idx = RawDB::create(&env, Some("resources-idx"), flags)?; + Ok(Self::new(env, db, idx)) + } + + pub fn lookup_id>(&self, id: S) -> Result> { let txn = self.env.begin_ro_txn()?; - self.id_index.get(&txn, &id.as_ref().as_bytes()).map(|ok| { + let id = self.id_index.get(&txn, &id.as_ref().as_bytes()).map(|ok| { ok.map(|num| *num) - }) + })?; + Ok(id) } } \ No newline at end of file diff --git a/src/db/state.rs b/src/db/state.rs index f876bc2..35574e3 100644 --- a/src/db/state.rs +++ b/src/db/state.rs @@ -75,6 +75,15 @@ impl StateDB { Ok(Self::new(env, input, output)) } + pub fn create>(path: P) -> lmdb::Result { + let flags = DatabaseFlags::empty(); + let env = Self::open_env(path)?; + let input = unsafe { DB::create(&env, Some("input"), flags)? }; + let output = unsafe { DB::create(&env, Some("output"), flags)? }; + + Ok(Self::new(env, input, output)) + } + fn update_txn(&self, txn: &mut RwTransaction, key: u64, input: &State, output: &State) -> Result<(), DBError> { diff --git a/src/db/typed.rs b/src/db/typed.rs index 038e4c3..60ff419 100644 --- a/src/db/typed.rs +++ b/src/db/typed.rs @@ -68,6 +68,7 @@ impl OutputBuffer for AllocSerializer { } } +// TODO: This should be possible to autoimplement for Sized Serializers pub trait OutputWriter: Fallible { fn write_into(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>; } @@ -141,13 +142,13 @@ impl DB } } - pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T) - -> Result, A>, A::Error> + pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T) + -> Result, A>, A::Error> { let c = self.db.open_ro_cursor(txn) .map_err(A::from_db_err)?; // Safe because we are providing both Adapter and cursor and know it matches - Ok(unsafe { Cursor::new(c) }) + Ok(unsafe { TypedCursor::new(c) }) } } @@ -190,12 +191,12 @@ impl<'a, A> DB } } -pub struct Cursor { +pub struct TypedCursor { cursor: C, phantom: PhantomData, } -impl<'txn, C, A> Cursor +impl<'txn, C, A> TypedCursor where C: lmdb::Cursor<'txn>, A: Adapter, { diff --git a/src/db/user.rs b/src/db/user.rs index b212647..8ada90c 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -1,142 +1,71 @@ -//! UserDB does two kinds of lookups: -//! 1. "I have this here username, what user is that" -//! 2. "I have this here user, what are their roles (and other associated data)" -use serde::{Serialize, Deserialize}; -use std::fmt; -use std::fs; use std::sync::Arc; -use std::iter::FromIterator; -use std::path::Path; -use crate::db::access::RoleIdentifier; -use std::collections::HashMap; +use super::{DB, AllocAdapter, Environment, Result}; +use crate::db::raw::RawDB; +use crate::db::{DatabaseFlags, LMDBorrow, RoTransaction, WriteFlags, }; -use slog::Logger; +use rkyv::{Archive, Serialize, Deserialize, Archived}; -use crate::error::Result; -use crate::config::Config; +type Adapter = AllocAdapter; +#[derive(Clone)] +pub struct UserDB { + env: Arc, + db: DB, +} -mod internal; -pub use internal::Internal; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -/// An user +#[derive(Debug, Clone, Archive, Serialize, Deserialize, serde::Serialize, serde::Deserialize)] pub struct User { - /// The precise (and unique) identifier of this user - pub id: UserId, - /// Data BFFH stores on this user to base decisions on - pub data: UserData, + id: u128, + username: String, + roles: Vec, } -impl User { - pub fn new(id: UserId, data: UserData) -> Self { - Self { id, data } +impl UserDB { + pub unsafe fn new(env: Arc, db: RawDB) -> Self { + let db = DB::new_unchecked(db); + Self { env, db } } -} -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -/// Authorization Identity -/// -/// This identity is internal to FabAccess and completely independent from the authentication -/// method or source -pub struct UserId { - /// Main User ID. Generally an user name or similar. Locally unique - pub uid: String, - /// Sub user ID. - /// - /// Can change scopes for permissions, e.g. having a +admin account with more permissions than - /// the default account and +dashboard et.al. accounts that have restricted permissions for - /// their applications - pub subuid: Option, - /// Realm this account originates. - /// - /// The Realm is usually described by a domain name but local policy may dictate an unrelated - /// mapping - pub realm: Option, -} - -impl UserId { - pub fn new(uid: String, subuid: Option, realm: Option) -> Self { - Self { uid, subuid, realm } + pub unsafe fn open(env: Arc) -> Result { + let db = RawDB::open(&env, Some("user"))?; + Ok(Self::new(env, db)) } -} -impl fmt::Display for UserId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let r = write!(f, "{}", self.uid); - if let Some(ref s) = self.subuid { - write!(f, "+{}", s)?; - } - if let Some(ref l) = self.realm { - write!(f, "@{}", l)?; - } - r + pub unsafe fn create(env: Arc) -> Result { + let flags = DatabaseFlags::empty(); + let db = RawDB::create(&env, Some("user"), flags)?; + Ok(Self::new(env, db)) } -} -#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -/// Data on an user to base decisions on -/// -/// This of course includes authorization data, i.e. that users set roles -pub struct UserData { - /// A Person has N ≥ 0 roles. - /// Persons are only ever given roles, not permissions directly - pub roles: Vec, - - #[serde(skip_serializing_if = "is_zero")] - #[serde(default = "default_priority")] - /// A priority number, defaulting to 0. - /// - /// The higher, the higher the priority. Higher priority users overwrite lower priority ones. - pub priority: u64, - - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub passwd: Option, - - /// Additional data storage - #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] - kv: HashMap, -} - -impl UserData { - pub fn new(roles: Vec, priority: u64) -> Self { - Self { roles, priority, kv: HashMap::new(), passwd: None } - } -} - -fn is_zero(i: &u64) -> bool { - *i == 0 -} -const fn default_priority() -> u64 { - 0 -} - -pub fn load_file>(path: P) -> Result> { - let f = fs::read(path)?; - let mut map: HashMap = toml::from_slice(&f)?; - - Ok(HashMap::from_iter(map.drain().map(|(uid, mut user_data)| { - user_data.passwd = user_data.passwd.map(|pw| if !pw.starts_with("$argon2") { - let config = argon2::Config::default(); - let salt: [u8; 16] = rand::random(); - let hash = argon2::hash_encoded(pw.as_bytes(), &salt, &config) - .expect(&format!("Failed to hash password for {}: ", uid)); - println!("Hashed pw for {} to {}", uid, hash); - - hash + pub fn get(&self, uid: &str) -> Result>>> { + let txn = self.env.begin_ro_txn()?; + if let Some(state) = self.db.get(&txn, &uid.as_bytes())? { + let ptr = state.into(); + Ok(Some(unsafe { LMDBorrow::new(ptr, txn) })) } else { - pw - }); - ( uid.clone() - , User::new(UserId::new(uid, None, None), user_data) - ) - }))) -} + Ok(None) + } + } -pub fn init(log: Logger, _config: &Config, env: Arc) -> Result { - let mut flags = lmdb::DatabaseFlags::empty(); - let db = env.create_db(Some("userdb"), flags)?; - debug!(&log, "Opened user db successfully."); + pub fn put(&self, uid: &str, user: &User) -> Result<()> { + let mut txn = self.env.begin_rw_txn()?; + let flags = WriteFlags::empty(); + self.db.put(&mut txn, &uid.as_bytes(), user, flags)?; + Ok(()) + } - Ok(Internal::new(log, env, db)) -} + pub fn get_all(&self) -> Result> { + let txn = self.env.begin_ro_txn()?; + let mut cursor = self.db.open_ro_cursor(&txn)?; + let iter = cursor.iter_start(); + let mut out = Vec::new(); + let mut deserializer = rkyv::Infallible; + for user in iter { + let (uid, user) = user?; + let uid = unsafe { std::str::from_utf8_unchecked(uid).to_string() }; + let user: User = user.deserialize(&mut deserializer).unwrap(); + out.push((uid, user)); + } + + Ok(out) + } +} \ No newline at end of file diff --git a/src/db/user/internal.rs b/src/db/user/internal.rs deleted file mode 100644 index fe649a8..0000000 --- a/src/db/user/internal.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::sync::Arc; - -use slog::Logger; -use lmdb::{Environment, Transaction, RwTransaction, Cursor}; - -use crate::error::Result; - -use super::*; - -#[derive(Clone, Debug)] -pub struct Internal { - log: Logger, - env: Arc, - db: lmdb::Database, -} - -impl Internal { - pub fn new(log: Logger, env: Arc, db: lmdb::Database) -> Self { - Self { log, env, db } - } - - pub fn get_user_txn(&self, txn: &T, uid: &str) -> Result> { - match txn.get(self.db, &uid.as_bytes()) { - Ok(bytes) => { - Ok(Some(flexbuffers::from_slice(bytes)?)) - }, - Err(lmdb::Error::NotFound) => Ok(None), - Err(e) => Err(e.into()), - } - } - pub fn get_user(&self, uid: &str) -> Result> { - let txn = self.env.begin_ro_txn()?; - self.get_user_txn(&txn, uid) - } - - pub fn put_user_txn(&self, txn: &mut RwTransaction, uid: &str, user: &User) -> Result<()> { - let bytes = flexbuffers::to_vec(user)?; - txn.put(self.db, &uid.as_bytes(), &bytes, lmdb::WriteFlags::empty())?; - - Ok(()) - } - pub fn put_user(&self, uid: &str, user: &User) -> Result<()> { - let mut txn = self.env.begin_rw_txn()?; - self.put_user_txn(&mut txn, uid, user)?; - txn.commit()?; - - Ok(()) - } - - pub fn list_users(&self) -> Result> { - let txn = self.env.begin_ro_txn()?; - Ok(self.list_users_txn(&txn)?.collect()) - } - pub fn list_users_txn(&self, txn: &T) -> Result> { - let mut cursor = txn.open_ro_cursor(self.db)?; - Ok(cursor.iter_start().map(|buf| { - let (_kbuf, vbuf) = buf.unwrap(); - flexbuffers::from_slice(vbuf).unwrap() - })) - } - - pub fn login(&self, uid: &str, password: &[u8]) -> Result> { - let txn = self.env.begin_ro_txn()?; - Ok(self.get_user_txn(&txn, uid)? - .filter(|user| { - user.data.passwd.is_some() - && argon2::verify_encoded(user.data.passwd.as_ref().unwrap(), password).unwrap_or(false) - })) - } -} diff --git a/src/error.rs b/src/error.rs index 2fa955e..15eabd0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ use rsasl::SaslError; use futures::task as futures_task; use paho_mqtt::errors as mqtt; +use crate::db::DBError; //FIXME use crate::network; @@ -18,7 +19,7 @@ pub enum Error { IO(io::Error), Boxed(Box), Capnp(capnp::Error), - LMDB(lmdb::Error), + DB(DBError), FuturesSpawn(futures_task::SpawnError), MQTT(mqtt::Error), BadVersion((u32,u32)), @@ -43,8 +44,8 @@ impl fmt::Display for Error { Error::Capnp(e) => { write!(f, "Cap'n Proto Error: {}", e) }, - Error::LMDB(e) => { - write!(f, "LMDB Error: {}", e) + Error::DB(e) => { + write!(f, "DB Error: {:?}", e) }, Error::FuturesSpawn(e) => { write!(f, "Future could not be spawned: {}", e) @@ -92,9 +93,9 @@ impl From for Error { } } -impl From for Error { - fn from(e: lmdb::Error) -> Error { - Error::LMDB(e) +impl From for Error { + fn from(e: DBError) -> Error { + Error::DB(e) } } diff --git a/src/lib.rs b/src/lib.rs index 98971c3..d0bfd20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ mod space; mod resource; mod schema; mod state; -mod db; +pub mod db; mod network; pub mod oid; mod varint; diff --git a/src/permissions.rs b/src/permissions.rs index 32e8efc..0d88da3 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -95,7 +95,11 @@ impl RoleIdentifier { impl fmt::Display for RoleIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}/{}", self.name, self.source) + if self.source != "" { + write!(f, "{}/{}", self.name, self.source) + } else { + write!(f, "{}", self.name) + } } } @@ -106,7 +110,7 @@ impl std::str::FromStr for RoleIdentifier { if let Some((name, source)) = split_once(s, '/') { Ok(RoleIdentifier { name: name.to_string(), source: source.to_string() }) } else { - Err(RoleFromStrError::Invalid) + Ok(RoleIdentifier { name: s.to_string(), source: String::new() }) } } }