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",
]
[[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"

View File

@ -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" }

View File

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

View File

@ -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(())
}

View File

@ -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<E>(self, v: &str) -> std::result::Result<Self::Value, E>

View File

@ -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<T, V> {
impl<'env, T, V> LMDBorrow<T, V>
where T: Transaction,
{
pub unsafe fn reborrow(ptr: &'_ V) -> NonNull<V> {
ptr.into()
}
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
}
}
@ -39,4 +40,4 @@ impl<'env, T, V> Deref for LMDBorrow<T, V>
// valid pointer so this is safe.
unsafe { self.ptr.as_ref() }
}
}
}

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
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(<AllocSerializer<1024> as Fallible>::Error),
}
pub(crate) type Result<T> = std::result::Result<T, DBError>;
impl From<lmdb::Error> for DBError {
fn from(e: lmdb::Error) -> Self {
Self::LMDB(e)
@ -112,4 +131,59 @@ impl<V: Serialize<AlignedSerializer<AlignedVec>>> Adapter for AlignedAdapter<V>
fn from_db_err(e: Error) -> <Self as Fallible>::Error {
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::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<String>;
#[derive(Clone)]
pub struct PassDB {
log: Logger,
env: Arc<Environment>,
db: lmdb::Database,
db: DB<Adapter>,
}
impl PassDB {
pub fn new(log: Logger, env: Arc<Environment>, db: lmdb::Database) -> Self {
Self { log, env, db }
pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {
let db = DB::new_unchecked(db);
Self { env, db }
}
pub fn init(log: Logger, env: Arc<Environment>) -> Result<Self> {
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<Environment>) -> Result<Self> {
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<T: Transaction>(&self, txn: &T, authcid: &str, password: &[u8]) -> Result<Option<bool>> {
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<Environment>) -> Result<Self> {
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<Option<bool>> {
pub fn check_pw<P: AsRef<[u8]>>(&self, uid: &str, inpass: P) -> Result<Option<bool>> {
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<P: AsRef<[u8]>>(&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<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::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<Environment>,
db: DB<AllocAdapter<Resource>>,
@ -32,10 +35,24 @@ impl ResourceDB {
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()?;
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)
}
}

View File

@ -75,6 +75,15 @@ impl StateDB {
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)
-> 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 {
fn write_into(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
}
@ -141,13 +142,13 @@ impl<A: Adapter> DB<A>
}
}
pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T)
-> Result<Cursor<lmdb::RoCursor<'txn>, A>, A::Error>
pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T)
-> Result<TypedCursor<lmdb::RoCursor<'txn>, 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<A>
}
}
pub struct Cursor<C, A> {
pub struct TypedCursor<C, A> {
cursor: C,
phantom: PhantomData<A>,
}
impl<'txn, C, A> Cursor<C, A>
impl<'txn, C, A> TypedCursor<C, A>
where C: lmdb::Cursor<'txn>,
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::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<User>;
#[derive(Clone)]
pub struct UserDB {
env: Arc<Environment>,
db: DB<Adapter>,
}
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<String>,
}
impl User {
pub fn new(id: UserId, data: UserData) -> Self {
Self { id, data }
impl UserDB {
pub unsafe fn new(env: Arc<Environment>, 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<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 fn new(uid: String, subuid: Option<String>, realm: Option<String>) -> Self {
Self { uid, subuid, realm }
pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
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<Environment>) -> Result<Self> {
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<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
pub fn get(&self, uid: &str) -> Result<Option<LMDBorrow<RoTransaction, Archived<User>>>> {
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<lmdb::Environment>) -> Result<Internal> {
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<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 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<dyn std::error::Error>),
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<capnp::Error> for Error {
}
}
impl From<lmdb::Error> for Error {
fn from(e: lmdb::Error) -> Error {
Error::LMDB(e)
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DB(e)
}
}

View File

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

View File

@ -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() })
}
}
}