mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-11-21 22:47:55 +01:00
All the things.
This commit is contained in:
parent
a7754f057b
commit
9e244aab7e
42
Cargo.lock
generated
42
Cargo.lock
generated
@ -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"
|
||||
|
@ -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" }
|
||||
|
@ -18,7 +18,7 @@
|
||||
, "notahostandnoport"
|
||||
]
|
||||
, machines = ./machines.dhall
|
||||
, db_path = "/tmp/bffh"
|
||||
, db_path = "/tmp/bffh/"
|
||||
, roles = ./roles.dhall
|
||||
, mqtt_url = "tcp://localhost:1883"
|
||||
}
|
||||
|
@ -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,19 +89,19 @@ fn main_res() -> Result<(), Error> {
|
||||
|
||||
println!("Final listens: {:?}", sockaddrs);
|
||||
|
||||
/*
|
||||
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 dbs = Databases::create(config.db_path)?;
|
||||
|
||||
let v = db.userdb.list_users()?;
|
||||
for user in v.iter() {
|
||||
tracing::info!("User {}:\n{:?}", user.id, user.data);
|
||||
if matches.is_present("dump") {
|
||||
let dump = Dump::new(&dbs)?;
|
||||
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") {
|
||||
let db = db::Databases::new(&log, &config)?;
|
||||
let mut dir = PathBuf::from(matches.value_of_os("load").unwrap());
|
||||
|
@ -106,7 +106,7 @@ 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\
|
||||
write!(formatter, "A string encoding a valid IP or Hostname (e.g. 127.0.0.1 or [::1]) with \
|
||||
or without a defined port")
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
@ -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()
|
||||
}))
|
||||
}
|
||||
}
|
@ -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)
|
||||
@ -113,3 +132,58 @@ impl<V: Serialize<AlignedSerializer<AlignedVec>>> Adapter for AlignedAdapter<V>
|
||||
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 })
|
||||
}
|
||||
}
|
@ -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)
|
||||
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 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 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");
|
||||
|
||||
pub fn insert_multiple(&self, vec: Vec<(String, String)>) -> Result<()> {
|
||||
let flags = WriteFlags::empty();
|
||||
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())?;
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>
|
||||
{
|
||||
|
@ -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>;
|
||||
}
|
||||
@ -142,12 +143,12 @@ 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>
|
||||
-> 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,
|
||||
{
|
||||
|
179
src/db/user.rs
179
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<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)?;
|
||||
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))
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}))
|
||||
}
|
||||
}
|
13
src/error.rs
13
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<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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ mod space;
|
||||
mod resource;
|
||||
mod schema;
|
||||
mod state;
|
||||
mod db;
|
||||
pub mod db;
|
||||
mod network;
|
||||
pub mod oid;
|
||||
mod varint;
|
||||
|
@ -95,7 +95,11 @@ impl RoleIdentifier {
|
||||
|
||||
impl fmt::Display for RoleIdentifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
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() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user