From 8db5580c90e5707bd87afa495a3d33ea4b83a17e Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Mon, 6 Dec 2021 21:53:42 +0100 Subject: [PATCH] Stuff --- api/src/lib.rs | 2 +- bffhd/db.rs | 12 +--- bffhd/db/resources.rs | 4 +- bffhd/lib.rs | 4 +- bffhd/resource/state/db.rs | 2 +- bffhd/resource/state/value.rs | 6 +- bffhd/server/authentication.rs | 83 +++++++++++++++++++++----- bffhd/server/mod.rs | 6 ++ bffhd/server/resources.rs | 19 ++++++ bffhd/server/session.rs | 23 ++++++++ bffhd/server/users.rs | 18 ++++++ bffhd/users/mod.rs | 40 ++++++++++++- bffhd/users/pass.rs | 103 +++++++++++++++++++++++++++++++++ bffhd/utils/oid.rs | 3 +- bffhd/utils/varint.rs | 2 +- i18n.toml | 4 ++ 16 files changed, 293 insertions(+), 38 deletions(-) create mode 100644 bffhd/server/resources.rs create mode 100644 bffhd/server/session.rs create mode 100644 bffhd/server/users.rs create mode 100644 bffhd/users/pass.rs create mode 100644 i18n.toml diff --git a/api/src/lib.rs b/api/src/lib.rs index 3e08d04..00e89d6 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -38,7 +38,7 @@ pub mod user { } pub mod users { - pub use crate::schema::users_capnp::*; + pub use crate::schema::users_capnp::users::*; } pub mod utils { diff --git a/bffhd/db.rs b/bffhd/db.rs index 30b4f1b..00070af 100644 --- a/bffhd/db.rs +++ b/bffhd/db.rs @@ -1,6 +1,4 @@ -use std::{ - marker::PhantomData, -}; +use std::marker::PhantomData; pub use lmdb::{ Environment, @@ -39,14 +37,10 @@ mod fix; pub use fix::LMDBorrow; mod resources; -pub use resources::{ - ResourceDB, -}; +pub use resources::ResourceDB; mod pass; -pub use pass::{ - PassDB, -}; +pub use pass::PassDB; use lmdb::Error; use rkyv::Deserialize; diff --git a/bffhd/db/resources.rs b/bffhd/db/resources.rs index e11913f..aac4323 100644 --- a/bffhd/db/resources.rs +++ b/bffhd/db/resources.rs @@ -1,8 +1,6 @@ use rkyv::{Archive, Serialize, Deserialize}; -use super::{ - DB, -}; +use super::DB; use crate::db::{AlignedAdapter, AllocAdapter}; use crate::db::raw::RawDB; use std::sync::Arc; diff --git a/bffhd/lib.rs b/bffhd/lib.rs index d81bb73..57702ea 100644 --- a/bffhd/lib.rs +++ b/bffhd/lib.rs @@ -1,5 +1,7 @@ -#![forbid(unused_imports)] +#![forbid(unused_imports, unused_import_braces)] #![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(missing_crate_level_docs)] //! Diflouroborane //! diff --git a/bffhd/resource/state/db.rs b/bffhd/resource/state/db.rs index b4a4279..c0d7bcc 100644 --- a/bffhd/resource/state/db.rs +++ b/bffhd/resource/state/db.rs @@ -3,7 +3,7 @@ use std::{ path::Path, }; -use rkyv::{Archived}; +use rkyv::Archived; use crate::db::{ DB, diff --git a/bffhd/resource/state/value.rs b/bffhd/resource/state/value.rs index 9ac03f7..d304f35 100644 --- a/bffhd/resource/state/value.rs +++ b/bffhd/resource/state/value.rs @@ -1,8 +1,6 @@ -use core::{ - ptr, -}; use std::{ fmt, + ptr, any::Any, hash::Hash, str::FromStr, @@ -14,7 +12,7 @@ use rkyv_typename::TypeName; use ptr_meta::{DynMetadata, Pointee}; use inventory; -use crate::utils::oid::{ObjectIdentifier}; +use crate::utils::oid::ObjectIdentifier; use rkyv::ser::{Serializer, ScratchSpace}; use std::collections::HashMap; use std::alloc::Layout; diff --git a/bffhd/server/authentication.rs b/bffhd/server/authentication.rs index 20a275e..51ecf11 100644 --- a/bffhd/server/authentication.rs +++ b/bffhd/server/authentication.rs @@ -5,7 +5,7 @@ use capnp::capability::Promise; use capnp::Error; use capnp_rpc::pry; -use rsasl::{gsasl_err_to_str, SaslError, Session}; +use rsasl::{rsasl_err_to_str, SASL, Session as SaslSession, Property, ReturnCode}; use rsasl::session::Step::{Done, NeedsMore}; use api::auth::authentication::{ @@ -19,17 +19,63 @@ use api::auth::response::{ Reason, Action, }; +use crate::users::{UserDB, PassDB}; +struct Callback; -pub struct Authentication { - state: State<()>, +struct AppData { + userdb: UserDB, + passdb: PassDB, } -enum State { +struct SessionData; + +impl rsasl::Callback for Callback { + fn callback(sasl: &mut SASL, + session: &mut SaslSession, + prop: Property + ) -> Result<(), ReturnCode> + { + match prop { + Property::GSASL_VALIDATE_SIMPLE => { + // Access the authentication id, i.e. the username to check the password for + let authcid = session + .get_property(Property::GSASL_AUTHID) + .ok_or(rsasl::GSASL_NO_AUTHID) + .map_err(|_| rsasl::GSASL_NO_AUTHID) + .and_then(|cstr| cstr.to_str() + .map_err(|_| rsasl::GSASL_NO_AUTHID))?; + + // Access the password itself + let password = session + .get_property(Property::GSASL_PASSWORD) + .ok_or(rsasl::GSASL_NO_PASSWORD) + .and_then(|cstr| cstr.to_str() + .map_err(|_| rsasl::GSASL_NO_AUTHID))?; + + let AppData { userdb: _, passdb } = sasl.retrieve_mut() + .ok_or(rsasl::GSASL_NO_CALLBACK)?; + + if let Ok(Some(Ok(true))) = passdb.verify_password(authcid, &password.as_bytes()) { + Ok(()) + } else { + Err(rsasl::GSASL_AUTHENTICATION_ERROR) + } + }, + _ => Err(rsasl::GSASL_NO_CALLBACK), + } + } +} + +pub struct Authentication { + state: State, +} + +enum State { InvalidMechanism, Finished, Aborted, - Running(Session) + Running(SaslSession) } impl Server for Authentication { @@ -37,19 +83,19 @@ impl Server for Authentication { use State::*; match self.state { InvalidMechanism => { - let mut builder = results.get(); + let builder = results.get(); let mut b = builder.init_error(); b.set_reason(Reason::BadMechanism); b.set_action(Action::Permanent); }, Finished => { - let mut builder = results.get(); + let builder = results.get(); let mut b = builder.init_error(); b.set_reason(Reason::Finished); b.set_action(Action::Permanent); }, Aborted => { - let mut builder = results.get(); + let builder = results.get(); let mut b = builder.init_error(); b.set_reason(Reason::Aborted); b.set_action(Action::Permanent); @@ -60,13 +106,19 @@ impl Server for Authentication { let mut builder = results.get(); match session.step(data) { - Ok(Done(Data)) => { + Ok(Done(data)) => { let mut b = builder.init_successful(); + if !data.is_empty() { + b.reborrow().set_additional_data(data.deref()) + } + let mut session_builder = b.init_session(); + let session = super::session::Session::new(); + session.build(&mut session_builder); }, - Ok(NeedsMore(Data)) => { - builder.set_challenge(Data.deref()); + Ok(NeedsMore(data)) => { + builder.set_challenge(data.deref()); }, - Err(e) => { + Err(_) => { let mut b = builder.init_error(); b.set_reason(Reason::Aborted); b.set_action(Action::Permanent); @@ -85,7 +137,7 @@ impl Server for Authentication { #[repr(transparent)] struct SaslE { - e: SaslError, + e: ReturnCode, } impl l10n_string::Server for SaslE { @@ -98,7 +150,8 @@ impl l10n_string::Server for SaslE { if lang == "en" { let mut builder = results.get(); builder.set_lang("en"); - builder.set_content(gsasl_err_to_str(self.e.0)); + builder.set_content(rsasl_err_to_str(self.e) + .unwrap_or("Unknown gsasl error")); } Promise::ok(()) @@ -109,7 +162,7 @@ impl l10n_string::Server for SaslE { _: l10n_string::AvailableParams, mut results: l10n_string::AvailableResults ) -> Promise<(), Error> { - let mut builder = results.get(); + let builder = results.get(); let mut langs = builder.init_langs(1); langs.set(0, "en"); Promise::ok(()) diff --git a/bffhd/server/mod.rs b/bffhd/server/mod.rs index 62703f3..0f93994 100644 --- a/bffhd/server/mod.rs +++ b/bffhd/server/mod.rs @@ -11,11 +11,17 @@ use api::bootstrap::{ mod tls; mod authentication; +mod session; +mod users; +mod resources; +#[derive(Debug)] +/// Cap'n Proto API Handler struct ApiSystem { } + impl Server for ApiSystem { fn mechanisms( &mut self, diff --git a/bffhd/server/resources.rs b/bffhd/server/resources.rs new file mode 100644 index 0000000..24105ed --- /dev/null +++ b/bffhd/server/resources.rs @@ -0,0 +1,19 @@ +use api::resources::resources::Server; + +#[derive(Debug, Clone)] +pub struct Resources { + +} + +impl Resources { + pub fn new() -> Self { + Self { + + } + } + +} + +impl Server for Resources { + +} \ No newline at end of file diff --git a/bffhd/server/session.rs b/bffhd/server/session.rs new file mode 100644 index 0000000..522265d --- /dev/null +++ b/bffhd/server/session.rs @@ -0,0 +1,23 @@ +use api::session::Builder; +use crate::server::resources::Resources; +use crate::server::users::Users; + +#[derive(Debug, Clone)] +pub struct Session { + resources: Resources, + users: Users, +} + +impl Session { + pub fn new() -> Self { + Session { + resources: Resources::new(), + users: Users::new(), + } + } + + pub fn build(&self, builder: &mut Builder) { + builder.set_resources(capnp_rpc::new_client(self.resources.clone())); + builder.set_users(capnp_rpc::new_client(self.users.clone())); + } +} \ No newline at end of file diff --git a/bffhd/server/users.rs b/bffhd/server/users.rs new file mode 100644 index 0000000..bb3c719 --- /dev/null +++ b/bffhd/server/users.rs @@ -0,0 +1,18 @@ +use api::users::Server; + +#[derive(Debug, Clone)] +pub struct Users { + +} + +impl Users { + pub fn new() -> Self { + Self { + + } + } +} + +impl Server for Users { + +} \ No newline at end of file diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index 7562b94..8e67674 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -2,17 +2,23 @@ use rkyv::{Archive, Serialize, Deserialize}; use capnp::capability::Promise; use capnp::Error; +use capnp_rpc::pry; use api::user::{ info, manage, admin, + passwd, }; mod db; +mod pass; + pub use db::UserDB; +pub use pass::PassDB; #[derive(Debug, Clone, Archive, Serialize, Deserialize, serde::Serialize, serde::Deserialize)] +/// User API endpoint pub struct User { id: u128, username: String, @@ -26,18 +32,50 @@ impl User { impl info::Server for User { fn list_roles( &mut self, - params: info::ListRolesParams, + _params: info::ListRolesParams, mut results: info::ListRolesResults ) -> Promise<(), Error> { + let results = results.get(); + let mut roles = results.init_roles(self.roles.len() as u32); + + for (i, role) in self.roles.iter().enumerate() { + let mut role_builder = roles.reborrow().get(i as u32); + role_builder.set_name(role); + } + Promise::ok(()) } } impl manage::Server for User { + fn add_role( + &mut self, + params: manage::AddRoleParams, + _: manage::AddRoleResults + ) -> Promise<(), Error> { + let params = pry!(params.get()); + let name = pry!(params.get_name()).to_string(); + self.roles.push(name); + Promise::ok(()) + } + fn remove_role( + &mut self, + params: manage::RemoveRoleParams, + _: manage::RemoveRoleResults + ) -> Promise<(), Error> { + let params = pry!(params.get()); + let name = pry!(params.get_name()); + self.roles.retain(|role| role != name); + Promise::ok(()) + } } impl admin::Server for User { +} + +impl passwd::Server for User { + } \ No newline at end of file diff --git a/bffhd/users/pass.rs b/bffhd/users/pass.rs new file mode 100644 index 0000000..5dc6568 --- /dev/null +++ b/bffhd/users/pass.rs @@ -0,0 +1,103 @@ +use std::sync::Arc; +use rand::RngCore; +use crate::db::{RawDB, DB, AllocAdapter, Environment, Result, DBError}; +use crate::db::{DatabaseFlags, WriteFlags}; + +use rkyv::{Serialize, Deserialize, Archived, Archive}; + +#[repr(transparent)] +#[derive(Debug, Clone, Eq, PartialEq, Archive, Serialize, Deserialize)] +pub struct Password(String); + +fn check_password(stored: &Archived, input: &[u8]) -> argon2::Result { + argon2::verify_encoded(stored.0.as_str(), input) +} + +type Adapter = AllocAdapter; + +#[derive(Clone, Debug)] +/// Internal Password Database +pub struct PassDB { + env: Arc, + db: DB, +} + +impl PassDB { + pub unsafe fn new(env: Arc, db: RawDB) -> Self { + let db = DB::new_unchecked(db); + Self { env, db } + } + + pub unsafe fn open(env: Arc) -> Result { + let db = RawDB::open(&env, Some("user"))?; + Ok(Self::new(env, db)) + } + + pub unsafe fn create(env: Arc) -> Result { + let flags = DatabaseFlags::empty(); + let db = RawDB::create(&env, Some("user"), flags)?; + Ok(Self::new(env, db)) + } + + /// Verify if the given password matches for the given user. + pub fn verify_password(&self, uid: &str, password: &[u8]) + -> Result>> + { + let txn = self.env.begin_ro_txn()?; + if let Some(stored) = self.db.get(&txn, &uid.as_bytes())? { + Ok(Some(check_password(stored, password))) + } else { + Ok(None) + } + } + + /// Set or update a password for a given uid. + /// + /// The given uid must not be "" and the given password must be 1..=1024 bytes. + pub fn set_password(&self, uid: &str, password: &[u8]) -> Result<()> { + debug_assert!(!uid.is_empty()); + debug_assert!(0 < password.len() && password.len() <= 1024); + + let config = argon2::Config::default(); + let mut salt: [u8; 16] = [0u8; 16]; + rand::thread_rng().fill_bytes(&mut salt); + let encoded = argon2::hash_encoded(password, &salt, &config) + .expect("Hashing given user password failed"); + let pwd = Password(encoded); + + let mut txn = self.env.begin_rw_txn()?; + self.db.put(&mut txn, &uid.as_bytes(), &pwd, WriteFlags::empty())?; + Ok(()) + } + + /// Delete an password entry from the database. + /// + /// Returns `Ok(false)` if no entry existed for the given user. + /// Thus, if this function returns `Ok(_)` you can be sure this db contains no password hash + /// for the given uid. + pub fn delete_password(&self, uid: &str) -> Result { + let mut txn = self.env.begin_rw_txn()?; + match self.db.del(&mut txn, &uid.as_bytes()) { + Ok(_) => Ok(true), + Err(DBError::LMDB(lmdb::Error::NotFound)) => Ok(false), + Err(e) => Err(e), + } + } + + /// Return all entries in this db in the form [(uid, password hash)]. + 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 passentry in iter { + let (uid, password) = passentry?; + let uid = unsafe { std::str::from_utf8_unchecked(uid).to_string() }; + let password: Password = password.deserialize(&mut deserializer).unwrap(); + out.push((uid, password)); + } + + Ok(out) + } +} \ No newline at end of file diff --git a/bffhd/utils/oid.rs b/bffhd/utils/oid.rs index 0077350..5d04ed4 100644 --- a/bffhd/utils/oid.rs +++ b/bffhd/utils/oid.rs @@ -52,10 +52,9 @@ //! [Object Identifiers]: https://en.wikipedia.org/wiki/Object_identifier //! [ITU]: https://en.wikipedia.org/wiki/International_Telecommunications_Union -use core::convert::{TryFrom }; - use rkyv::{Archive, Serialize}; use rkyv::vec::{ArchivedVec, VecResolver}; +use std::convert::TryFrom; use std::ops::Deref; use std::fmt; use std::fmt::Formatter; diff --git a/bffhd/utils/varint.rs b/bffhd/utils/varint.rs index e6e44bf..c94d4aa 100644 --- a/bffhd/utils/varint.rs +++ b/bffhd/utils/varint.rs @@ -1,5 +1,5 @@ use std::default::Default; -use std::ops::{Deref}; +use std::ops::Deref; #[derive(Debug)] pub struct VarUInt { diff --git a/i18n.toml b/i18n.toml new file mode 100644 index 0000000..1cdffcb --- /dev/null +++ b/i18n.toml @@ -0,0 +1,4 @@ +fallback-language = "en-GB" + +[fluent] +assets_dir = "i18n" \ No newline at end of file