All the things.

This commit is contained in:
Nadja Reitzenstein 2021-10-20 18:37:50 +02:00
parent a7754f057b
commit 9e244aab7e
19 changed files with 332 additions and 489 deletions

42
Cargo.lock generated
View File

@ -70,6 +70,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.5.2"
@ -208,6 +214,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "2.0.0-dev" version = "2.0.0-dev"
@ -247,6 +259,17 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.7.3" version = "0.7.3"
@ -450,6 +473,12 @@ dependencies = [
"cache-padded", "cache-padded",
] ]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.1" version = "0.2.1"
@ -550,6 +579,7 @@ dependencies = [
"rkyv_dyn", "rkyv_dyn",
"rkyv_typename", "rkyv_typename",
"rsasl", "rsasl",
"rust-argon2",
"serde", "serde",
"serde_dhall", "serde_dhall",
"serde_json", "serde_json",
@ -1641,6 +1671,18 @@ dependencies = [
"libc", "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]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"

View File

@ -70,8 +70,8 @@ async-trait = "0.1.51"
lazy_static = "1.4.0" lazy_static = "1.4.0"
#rust-argon2 = "0.8.3" rust-argon2 = "0.8.3"
#rand = "0.8.4" rand = "0.8.4"
[build-dependencies] [build-dependencies]
capnpc = "0.14.4" capnpc = "0.14.4"
@ -82,7 +82,6 @@ walkdir = "2.3.2"
futures-test = "0.3.16" futures-test = "0.3.16"
tempfile = "3.2" tempfile = "3.2"
bincode = "2.0.0-dev" bincode = "2.0.0-dev"
rand = "0.8"
[patch.crates-io] [patch.crates-io]
bincode = { git = "https://github.com/dequbed/bincode.git", branch = "feature/in_place_buffer" } bincode = { git = "https://github.com/dequbed/bincode.git", branch = "feature/in_place_buffer" }

View File

@ -18,7 +18,7 @@
, "notahostandnoport" , "notahostandnoport"
] ]
, machines = ./machines.dhall , machines = ./machines.dhall
, db_path = "/tmp/bffh" , db_path = "/tmp/bffh/"
, roles = ./roles.dhall , roles = ./roles.dhall
, mqtt_url = "tcp://localhost:1883" , mqtt_url = "tcp://localhost:1883"
} }

View File

@ -6,6 +6,7 @@ use std::{
use clap::{App, Arg, crate_version, crate_description, crate_name}; use clap::{App, Arg, crate_version, crate_description, crate_name};
use std::str::FromStr; use std::str::FromStr;
use diflouroborane::{config, error::Error}; use diflouroborane::{config, error::Error};
use diflouroborane::db::{Databases, Dump};
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
fn main_res() -> Result<(), Error> { fn main_res() -> Result<(), Error> {
@ -88,19 +89,19 @@ fn main_res() -> Result<(), Error> {
println!("Final listens: {:?}", sockaddrs); 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 v = db.userdb.list_users()?; if matches.is_present("dump") {
for user in v.iter() { let dump = Dump::new(&dbs)?;
tracing::info!("User {}:\n{:?}", user.id, user.data); let encoded = serde_json::to_vec(&dump).unwrap();
// 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();
} }
Ok(()) /*
} else if matches.is_present("load") { } else if matches.is_present("load") {
let db = db::Databases::new(&log, &config)?; let db = db::Databases::new(&log, &config)?;
let mut dir = PathBuf::from(matches.value_of_os("load").unwrap()); let mut dir = PathBuf::from(matches.value_of_os("load").unwrap());

View File

@ -3,7 +3,7 @@ use std::{
ops::Deref, ops::Deref,
}; };
use lmdb::Transaction; use crate::db::Transaction;
/// Memory Fixpoint for a value in the DB /// Memory Fixpoint for a value in the DB
/// ///
@ -22,11 +22,12 @@ pub struct LMDBorrow<T, V> {
impl<'env, T, V> LMDBorrow<T, V> impl<'env, T, V> LMDBorrow<T, V>
where T: Transaction, where T: Transaction,
{ {
pub unsafe fn reborrow(ptr: &'_ V) -> NonNull<V> {
ptr.into()
}
pub unsafe fn new(ptr: NonNull<V>, txn: T) -> Self { pub unsafe fn new(ptr: NonNull<V>, txn: T) -> Self {
Self { ptr: ptr.into(), txn, } Self { ptr: ptr.into(), txn }
}
pub fn unwrap_txn(self) -> T {
self.txn
} }
} }

View File

@ -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<UserId>),
/// 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<UserId>) -> 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<lmdb::Environment>) -> Result<Internal> {
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))
}

View File

@ -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<Environment>,
db: lmdb::Database,
}
impl Internal {
pub fn new(log: Logger, env: Arc<Environment>, db: lmdb::Database) -> Self {
Self { log, env, db }
}
pub fn get_with_txn<T: Transaction>(&self, txn: &T, id: &String)
-> Result<Option<MachineState>>
{
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<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: &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<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| {
let (_kbuf, vbuf) = buf.unwrap();
flexbuffers::from_slice(vbuf).unwrap()
}))
}
}

View File

@ -23,7 +23,7 @@ mod typed;
// re-exports // re-exports
pub use typed::{ pub use typed::{
DB, DB,
Cursor, TypedCursor,
Adapter, Adapter,
OutputBuffer, OutputBuffer,
@ -49,8 +49,25 @@ pub use resources::{
ResourceDB, ResourceDB,
}; };
mod pass;
pub use pass::{
PassDB,
};
mod user;
pub use user::{
UserDB,
};
use lmdb::Error; use lmdb::Error;
use rkyv::ser::serializers::AlignedSerializer; 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)] #[derive(Debug)]
pub enum DBError { pub enum DBError {
@ -58,6 +75,8 @@ pub enum DBError {
RKYV(<AllocSerializer<1024> as Fallible>::Error), RKYV(<AllocSerializer<1024> as Fallible>::Error),
} }
pub(crate) type Result<T> = std::result::Result<T, DBError>;
impl From<lmdb::Error> for DBError { impl From<lmdb::Error> for DBError {
fn from(e: lmdb::Error) -> Self { fn from(e: lmdb::Error) -> Self {
Self::LMDB(e) Self::LMDB(e)
@ -113,3 +132,58 @@ impl<V: Serialize<AlignedSerializer<AlignedVec>>> Adapter for AlignedAdapter<V>
e e
} }
} }
pub struct Databases {
pub userdb: UserDB,
pub passdb: PassDB,
pub resourcedb: ResourceDB,
pub statedb: StateDB,
}
impl Databases {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
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<P: AsRef<Path>>(path: P) -> Result<Self> {
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<String, User>,
passwds: HashMap<String, String>,
resources: HashMap<String, Resource>,
states: HashMap<String, (State, State)>,
}
impl Dump {
pub fn new(dbs: &Databases) -> Result<Self> {
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 })
}
}

View File

@ -1,78 +1,58 @@
use std::sync::Arc; use std::sync::Arc;
use std::path::Path; use super::Environment;
use std::fs; use super::AllocAdapter;
use std::collections::HashMap; use super::DB;
use super::raw::RawDB;
use super::{DatabaseFlags, WriteFlags};
use crate::db::Result;
use super::Transaction;
use argon2; use argon2;
use lmdb::{Environment, Transaction, RwTransaction};
use slog::Logger;
use crate::error::Result;
type Adapter = AllocAdapter<String>;
#[derive(Clone)]
pub struct PassDB { pub struct PassDB {
log: Logger,
env: Arc<Environment>, env: Arc<Environment>,
db: lmdb::Database, db: DB<Adapter>,
} }
impl PassDB { impl PassDB {
pub fn new(log: Logger, env: Arc<Environment>, db: lmdb::Database) -> Self { pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {
Self { log, env, db } let db = DB::new_unchecked(db);
Self { env, db }
} }
pub fn init(log: Logger, env: Arc<Environment>) -> Result<Self> { pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
let mut flags = lmdb::DatabaseFlags::empty(); let db = RawDB::open(&env, Some("pass"))?;
flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); Ok(Self::new(env, db))
let db = env.create_db(Some("pass"), flags)?;
Ok(Self::new(log, env, db))
} }
/// Check a password for a given authcid. pub unsafe fn create(env: Arc<Environment>) -> Result<Self> {
/// let flags = DatabaseFlags::empty();
/// `Ok(None)` means the given authcid is not stored in the database let db = RawDB::create(&env, Some("pass"), flags)?;
pub fn check_with_txn<T: Transaction>(&self, txn: &T, authcid: &str, password: &[u8]) -> Result<Option<bool>> { Ok(Self::new(env, db))
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 fn check(&self, authcid: &str, password: &[u8]) -> Result<Option<bool>> { pub fn check_pw<P: AsRef<[u8]>>(&self, uid: &str, inpass: P) -> Result<Option<bool>> {
let txn = self.env.begin_ro_txn()?; let txn = self.env.begin_ro_txn()?;
self.check_with_txn(&txn, authcid, password) 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)
}
} }
/// Store a password for a given authcid, potentially overwriting an existing password pub fn set_password<P: AsRef<[u8]>>(&self, uid: &str, password: P) -> Result<()> {
pub fn store_with_txn(&self, txn: &mut RwTransaction, authcid: &str, password: &[u8]) -> Result<()> { let cfg = argon2::Config::default();
let config = argon2::Config::default(); let salt: [u8; 10] = rand::random();
let salt: [u8; 16] = rand::random(); let enc = argon2::hash_encoded(password.as_ref(), &salt, &cfg)
let hash = argon2::hash_encoded(password, &salt, &config)?; .expect("Hashing password failed for static valid 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 flags = WriteFlags::empty();
let mut txn = self.env.begin_rw_txn()?; let mut txn = self.env.begin_rw_txn()?;
for (authcid, password) in vec.iter() { self.db.put(&mut txn, &uid.as_bytes(), &enc, flags)?;
self.store_with_txn(&mut txn, authcid.as_ref(), password.as_bytes())?;
}
txn.commit()?; txn.commit()?;
let v: Vec<&String> = vec.iter().map(|(a,_)| a).collect();
debug!(self.log, "Loaded passwords for: {:?}", v);
Ok(()) Ok(())
} }
pub fn load_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let f = fs::read(path)?;
let mut map: HashMap<String, String> = toml::from_slice(&f)?;
self.insert_multiple(map.drain().collect())
}
} }

View File

@ -7,10 +7,12 @@ use super::{
use crate::db::AlignedAdapter; use crate::db::AlignedAdapter;
use crate::db::raw::RawDB; use crate::db::raw::RawDB;
use std::sync::Arc; use std::sync::Arc;
use lmdb::Environment; use crate::db::{Environment, DatabaseFlags};
use crate::db; use crate::db::Result;
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Archive, Serialize, Deserialize)] #[derive(Archive, Serialize, Deserialize)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Resource { pub struct Resource {
uuid: u128, uuid: u128,
id: String, id: String,
@ -18,6 +20,7 @@ pub struct Resource {
description_idx: u64, description_idx: u64,
} }
#[derive(Clone)]
pub struct ResourceDB { pub struct ResourceDB {
env: Arc<Environment>, env: Arc<Environment>,
db: DB<AllocAdapter<Resource>>, db: DB<AllocAdapter<Resource>>,
@ -32,10 +35,24 @@ impl ResourceDB {
Self { env, db, id_index } Self { env, db, id_index }
} }
pub fn lookup_id<S: AsRef<str>>(&self, id: S) -> Result<Option<u64>, db::Error> { pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
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<Environment>) -> Result<Self> {
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<S: AsRef<str>>(&self, id: S) -> Result<Option<u64>> {
let txn = self.env.begin_ro_txn()?; 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.map(|num| *num)
}) })?;
Ok(id)
} }
} }

View File

@ -75,6 +75,15 @@ impl StateDB {
Ok(Self::new(env, input, output)) Ok(Self::new(env, input, output))
} }
pub fn create<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
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) fn update_txn(&self, txn: &mut RwTransaction, key: u64, input: &State, output: &State)
-> Result<(), DBError> -> Result<(), DBError>
{ {

View File

@ -68,6 +68,7 @@ impl<const N: usize> OutputBuffer for AllocSerializer<N> {
} }
} }
// TODO: This should be possible to autoimplement for Sized Serializers
pub trait OutputWriter: Fallible { pub trait OutputWriter: Fallible {
fn write_into(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>; fn write_into(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
} }
@ -142,12 +143,12 @@ impl<A: Adapter> DB<A>
} }
pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T) pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T)
-> Result<Cursor<lmdb::RoCursor<'txn>, A>, A::Error> -> Result<TypedCursor<lmdb::RoCursor<'txn>, A>, A::Error>
{ {
let c = self.db.open_ro_cursor(txn) let c = self.db.open_ro_cursor(txn)
.map_err(A::from_db_err)?; .map_err(A::from_db_err)?;
// Safe because we are providing both Adapter and cursor and know it matches // 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<A>
} }
} }
pub struct Cursor<C, A> { pub struct TypedCursor<C, A> {
cursor: C, cursor: C,
phantom: PhantomData<A>, phantom: PhantomData<A>,
} }
impl<'txn, C, A> Cursor<C, A> impl<'txn, C, A> TypedCursor<C, A>
where C: lmdb::Cursor<'txn>, where C: lmdb::Cursor<'txn>,
A: Adapter, A: Adapter,
{ {

View File

@ -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::sync::Arc;
use std::iter::FromIterator; use super::{DB, AllocAdapter, Environment, Result};
use std::path::Path; use crate::db::raw::RawDB;
use crate::db::access::RoleIdentifier; use crate::db::{DatabaseFlags, LMDBorrow, RoTransaction, WriteFlags, };
use std::collections::HashMap;
use slog::Logger; use rkyv::{Archive, Serialize, Deserialize, Archived};
use crate::error::Result; type Adapter = AllocAdapter<User>;
use crate::config::Config; #[derive(Clone)]
pub struct UserDB {
env: Arc<Environment>,
db: DB<Adapter>,
}
mod internal; #[derive(Debug, Clone, Archive, Serialize, Deserialize, serde::Serialize, serde::Deserialize)]
pub use internal::Internal;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
/// An user
pub struct User { pub struct User {
/// The precise (and unique) identifier of this user id: u128,
pub id: UserId, username: String,
/// Data BFFH stores on this user to base decisions on roles: Vec<String>,
pub data: UserData,
} }
impl User { impl UserDB {
pub fn new(id: UserId, data: UserData) -> Self { pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {
Self { id, data } let db = DB::new_unchecked(db);
} Self { env, db }
} }
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
/// Authorization Identity let db = RawDB::open(&env, Some("user"))?;
/// Ok(Self::new(env, db))
/// 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<String>,
/// Realm this account originates.
///
/// The Realm is usually described by a domain name but local policy may dictate an unrelated
/// mapping
pub realm: Option<String>,
} }
impl UserId { pub unsafe fn create(env: Arc<Environment>) -> Result<Self> {
pub fn new(uid: String, subuid: Option<String>, realm: Option<String>) -> Self { let flags = DatabaseFlags::empty();
Self { uid, subuid, realm } let db = RawDB::create(&env, Some("user"), flags)?;
} Ok(Self::new(env, db))
} }
impl fmt::Display for UserId { pub fn get(&self, uid: &str) -> Result<Option<LMDBorrow<RoTransaction, Archived<User>>>> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let txn = self.env.begin_ro_txn()?;
let r = write!(f, "{}", self.uid); if let Some(state) = self.db.get(&txn, &uid.as_bytes())? {
if let Some(ref s) = self.subuid { let ptr = state.into();
write!(f, "+{}", s)?; Ok(Some(unsafe { LMDBorrow::new(ptr, txn) }))
}
if let Some(ref l) = self.realm {
write!(f, "@{}", l)?;
}
r
}
}
#[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<RoleIdentifier>,
#[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<String>,
/// Additional data storage
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
kv: HashMap<String, String>,
}
impl UserData {
pub fn new(roles: Vec<RoleIdentifier>, 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<P: AsRef<Path>>(path: P) -> Result<HashMap<String, User>> {
let f = fs::read(path)?;
let mut map: HashMap<String, UserData> = 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
} else { } else {
pw Ok(None)
}); }
( uid.clone()
, User::new(UserId::new(uid, None, None), user_data)
)
})))
} }
pub fn init(log: Logger, _config: &Config, env: Arc<lmdb::Environment>) -> Result<Internal> { pub fn put(&self, uid: &str, user: &User) -> Result<()> {
let mut flags = lmdb::DatabaseFlags::empty(); let mut txn = self.env.begin_rw_txn()?;
let db = env.create_db(Some("userdb"), flags)?; let flags = WriteFlags::empty();
debug!(&log, "Opened user db successfully."); self.db.put(&mut txn, &uid.as_bytes(), user, flags)?;
Ok(())
Ok(Internal::new(log, env, db)) }
pub fn get_all(&self) -> Result<Vec<(String, User)>> {
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)
}
} }

View File

@ -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<Environment>,
db: lmdb::Database,
}
impl Internal {
pub fn new(log: Logger, env: Arc<Environment>, db: lmdb::Database) -> Self {
Self { log, env, db }
}
pub fn get_user_txn<T: Transaction>(&self, txn: &T, uid: &str) -> Result<Option<User>> {
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<Option<User>> {
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<Vec<User>> {
let txn = self.env.begin_ro_txn()?;
Ok(self.list_users_txn(&txn)?.collect())
}
pub fn list_users_txn<T: Transaction>(&self, txn: &T) -> Result<impl Iterator<Item=User>> {
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<Option<User>> {
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)
}))
}
}

View File

@ -8,6 +8,7 @@ use rsasl::SaslError;
use futures::task as futures_task; use futures::task as futures_task;
use paho_mqtt::errors as mqtt; use paho_mqtt::errors as mqtt;
use crate::db::DBError;
//FIXME use crate::network; //FIXME use crate::network;
@ -18,7 +19,7 @@ pub enum Error {
IO(io::Error), IO(io::Error),
Boxed(Box<dyn std::error::Error>), Boxed(Box<dyn std::error::Error>),
Capnp(capnp::Error), Capnp(capnp::Error),
LMDB(lmdb::Error), DB(DBError),
FuturesSpawn(futures_task::SpawnError), FuturesSpawn(futures_task::SpawnError),
MQTT(mqtt::Error), MQTT(mqtt::Error),
BadVersion((u32,u32)), BadVersion((u32,u32)),
@ -43,8 +44,8 @@ impl fmt::Display for Error {
Error::Capnp(e) => { Error::Capnp(e) => {
write!(f, "Cap'n Proto Error: {}", e) write!(f, "Cap'n Proto Error: {}", e)
}, },
Error::LMDB(e) => { Error::DB(e) => {
write!(f, "LMDB Error: {}", e) write!(f, "DB Error: {:?}", e)
}, },
Error::FuturesSpawn(e) => { Error::FuturesSpawn(e) => {
write!(f, "Future could not be spawned: {}", e) write!(f, "Future could not be spawned: {}", e)
@ -92,9 +93,9 @@ impl From<capnp::Error> for Error {
} }
} }
impl From<lmdb::Error> for Error { impl From<DBError> for Error {
fn from(e: lmdb::Error) -> Error { fn from(e: DBError) -> Error {
Error::LMDB(e) Error::DB(e)
} }
} }

View File

@ -16,7 +16,7 @@ mod space;
mod resource; mod resource;
mod schema; mod schema;
mod state; mod state;
mod db; pub mod db;
mod network; mod network;
pub mod oid; pub mod oid;
mod varint; mod varint;

View File

@ -95,7 +95,11 @@ impl RoleIdentifier {
impl fmt::Display for RoleIdentifier { impl fmt::Display for RoleIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.source != "" {
write!(f, "{}/{}", self.name, 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, '/') { if let Some((name, source)) = split_once(s, '/') {
Ok(RoleIdentifier { name: name.to_string(), source: source.to_string() }) Ok(RoleIdentifier { name: name.to_string(), source: source.to_string() })
} else { } else {
Err(RoleFromStrError::Invalid) Ok(RoleIdentifier { name: s.to_string(), source: String::new() })
} }
} }
} }