This commit is contained in:
Nadja Reitzenstein 2021-12-06 21:53:42 +01:00
parent bedde0e19f
commit 8db5580c90
16 changed files with 293 additions and 38 deletions

View File

@ -38,7 +38,7 @@ pub mod user {
} }
pub mod users { pub mod users {
pub use crate::schema::users_capnp::*; pub use crate::schema::users_capnp::users::*;
} }
pub mod utils { pub mod utils {

View File

@ -1,6 +1,4 @@
use std::{ use std::marker::PhantomData;
marker::PhantomData,
};
pub use lmdb::{ pub use lmdb::{
Environment, Environment,
@ -39,14 +37,10 @@ mod fix;
pub use fix::LMDBorrow; pub use fix::LMDBorrow;
mod resources; mod resources;
pub use resources::{ pub use resources::ResourceDB;
ResourceDB,
};
mod pass; mod pass;
pub use pass::{ pub use pass::PassDB;
PassDB,
};
use lmdb::Error; use lmdb::Error;
use rkyv::Deserialize; use rkyv::Deserialize;

View File

@ -1,8 +1,6 @@
use rkyv::{Archive, Serialize, Deserialize}; use rkyv::{Archive, Serialize, Deserialize};
use super::{ use super::DB;
DB,
};
use crate::db::{AlignedAdapter, AllocAdapter}; use crate::db::{AlignedAdapter, AllocAdapter};
use crate::db::raw::RawDB; use crate::db::raw::RawDB;
use std::sync::Arc; use std::sync::Arc;

View File

@ -1,5 +1,7 @@
#![forbid(unused_imports)] #![forbid(unused_imports, unused_import_braces)]
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(missing_crate_level_docs)]
//! Diflouroborane //! Diflouroborane
//! //!

View File

@ -3,7 +3,7 @@ use std::{
path::Path, path::Path,
}; };
use rkyv::{Archived}; use rkyv::Archived;
use crate::db::{ use crate::db::{
DB, DB,

View File

@ -1,8 +1,6 @@
use core::{
ptr,
};
use std::{ use std::{
fmt, fmt,
ptr,
any::Any, any::Any,
hash::Hash, hash::Hash,
str::FromStr, str::FromStr,
@ -14,7 +12,7 @@ use rkyv_typename::TypeName;
use ptr_meta::{DynMetadata, Pointee}; use ptr_meta::{DynMetadata, Pointee};
use inventory; use inventory;
use crate::utils::oid::{ObjectIdentifier}; use crate::utils::oid::ObjectIdentifier;
use rkyv::ser::{Serializer, ScratchSpace}; use rkyv::ser::{Serializer, ScratchSpace};
use std::collections::HashMap; use std::collections::HashMap;
use std::alloc::Layout; use std::alloc::Layout;

View File

@ -5,7 +5,7 @@ use capnp::capability::Promise;
use capnp::Error; use capnp::Error;
use capnp_rpc::pry; 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 rsasl::session::Step::{Done, NeedsMore};
use api::auth::authentication::{ use api::auth::authentication::{
@ -19,17 +19,63 @@ use api::auth::response::{
Reason, Reason,
Action, Action,
}; };
use crate::users::{UserDB, PassDB};
struct Callback;
pub struct Authentication { struct AppData {
state: State<()>, userdb: UserDB,
passdb: PassDB,
} }
enum State<D> { struct SessionData;
impl rsasl::Callback<AppData, SessionData> for Callback {
fn callback(sasl: &mut SASL<AppData, SessionData>,
session: &mut SaslSession<SessionData>,
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<SessionData>,
}
enum State<E> {
InvalidMechanism, InvalidMechanism,
Finished, Finished,
Aborted, Aborted,
Running(Session<D>) Running(SaslSession<E>)
} }
impl Server for Authentication { impl Server for Authentication {
@ -37,19 +83,19 @@ impl Server for Authentication {
use State::*; use State::*;
match self.state { match self.state {
InvalidMechanism => { InvalidMechanism => {
let mut builder = results.get(); let builder = results.get();
let mut b = builder.init_error(); let mut b = builder.init_error();
b.set_reason(Reason::BadMechanism); b.set_reason(Reason::BadMechanism);
b.set_action(Action::Permanent); b.set_action(Action::Permanent);
}, },
Finished => { Finished => {
let mut builder = results.get(); let builder = results.get();
let mut b = builder.init_error(); let mut b = builder.init_error();
b.set_reason(Reason::Finished); b.set_reason(Reason::Finished);
b.set_action(Action::Permanent); b.set_action(Action::Permanent);
}, },
Aborted => { Aborted => {
let mut builder = results.get(); let builder = results.get();
let mut b = builder.init_error(); let mut b = builder.init_error();
b.set_reason(Reason::Aborted); b.set_reason(Reason::Aborted);
b.set_action(Action::Permanent); b.set_action(Action::Permanent);
@ -60,13 +106,19 @@ impl Server for Authentication {
let mut builder = results.get(); let mut builder = results.get();
match session.step(data) { match session.step(data) {
Ok(Done(Data)) => { Ok(Done(data)) => {
let mut b = builder.init_successful(); 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)) => { Ok(NeedsMore(data)) => {
builder.set_challenge(Data.deref()); builder.set_challenge(data.deref());
}, },
Err(e) => { Err(_) => {
let mut b = builder.init_error(); let mut b = builder.init_error();
b.set_reason(Reason::Aborted); b.set_reason(Reason::Aborted);
b.set_action(Action::Permanent); b.set_action(Action::Permanent);
@ -85,7 +137,7 @@ impl Server for Authentication {
#[repr(transparent)] #[repr(transparent)]
struct SaslE { struct SaslE {
e: SaslError, e: ReturnCode,
} }
impl l10n_string::Server for SaslE { impl l10n_string::Server for SaslE {
@ -98,7 +150,8 @@ impl l10n_string::Server for SaslE {
if lang == "en" { if lang == "en" {
let mut builder = results.get(); let mut builder = results.get();
builder.set_lang("en"); 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(()) Promise::ok(())
@ -109,7 +162,7 @@ impl l10n_string::Server for SaslE {
_: l10n_string::AvailableParams, _: l10n_string::AvailableParams,
mut results: l10n_string::AvailableResults mut results: l10n_string::AvailableResults
) -> Promise<(), Error> { ) -> Promise<(), Error> {
let mut builder = results.get(); let builder = results.get();
let mut langs = builder.init_langs(1); let mut langs = builder.init_langs(1);
langs.set(0, "en"); langs.set(0, "en");
Promise::ok(()) Promise::ok(())

View File

@ -11,11 +11,17 @@ use api::bootstrap::{
mod tls; mod tls;
mod authentication; mod authentication;
mod session;
mod users;
mod resources;
#[derive(Debug)]
/// Cap'n Proto API Handler
struct ApiSystem { struct ApiSystem {
} }
impl Server for ApiSystem { impl Server for ApiSystem {
fn mechanisms( fn mechanisms(
&mut self, &mut self,

19
bffhd/server/resources.rs Normal file
View File

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

23
bffhd/server/session.rs Normal file
View File

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

18
bffhd/server/users.rs Normal file
View File

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

View File

@ -2,17 +2,23 @@ use rkyv::{Archive, Serialize, Deserialize};
use capnp::capability::Promise; use capnp::capability::Promise;
use capnp::Error; use capnp::Error;
use capnp_rpc::pry;
use api::user::{ use api::user::{
info, info,
manage, manage,
admin, admin,
passwd,
}; };
mod db; mod db;
mod pass;
pub use db::UserDB; pub use db::UserDB;
pub use pass::PassDB;
#[derive(Debug, Clone, Archive, Serialize, Deserialize, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Archive, Serialize, Deserialize, serde::Serialize, serde::Deserialize)]
/// User API endpoint
pub struct User { pub struct User {
id: u128, id: u128,
username: String, username: String,
@ -26,18 +32,50 @@ impl User {
impl info::Server for User { impl info::Server for User {
fn list_roles( fn list_roles(
&mut self, &mut self,
params: info::ListRolesParams, _params: info::ListRolesParams,
mut results: info::ListRolesResults mut results: info::ListRolesResults
) -> Promise<(), Error> ) -> 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(()) Promise::ok(())
} }
} }
impl manage::Server for User { 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 admin::Server for User {
}
impl passwd::Server for User {
} }

103
bffhd/users/pass.rs Normal file
View File

@ -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<Password>, input: &[u8]) -> argon2::Result<bool> {
argon2::verify_encoded(stored.0.as_str(), input)
}
type Adapter = AllocAdapter<Password>;
#[derive(Clone, Debug)]
/// Internal Password Database
pub struct PassDB {
env: Arc<Environment>,
db: DB<Adapter>,
}
impl PassDB {
pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {
let db = DB::new_unchecked(db);
Self { env, db }
}
pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
let db = RawDB::open(&env, Some("user"))?;
Ok(Self::new(env, db))
}
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))
}
/// Verify if the given password matches for the given user.
pub fn verify_password(&self, uid: &str, password: &[u8])
-> Result<Option<argon2::Result<bool>>>
{
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<bool> {
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<Vec<(String, Password)>> {
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)
}
}

View File

@ -52,10 +52,9 @@
//! [Object Identifiers]: https://en.wikipedia.org/wiki/Object_identifier //! [Object Identifiers]: https://en.wikipedia.org/wiki/Object_identifier
//! [ITU]: https://en.wikipedia.org/wiki/International_Telecommunications_Union //! [ITU]: https://en.wikipedia.org/wiki/International_Telecommunications_Union
use core::convert::{TryFrom };
use rkyv::{Archive, Serialize}; use rkyv::{Archive, Serialize};
use rkyv::vec::{ArchivedVec, VecResolver}; use rkyv::vec::{ArchivedVec, VecResolver};
use std::convert::TryFrom;
use std::ops::Deref; use std::ops::Deref;
use std::fmt; use std::fmt;
use std::fmt::Formatter; use std::fmt::Formatter;

View File

@ -1,5 +1,5 @@
use std::default::Default; use std::default::Default;
use std::ops::{Deref}; use std::ops::Deref;
#[derive(Debug)] #[derive(Debug)]
pub struct VarUInt<const N: usize> { pub struct VarUInt<const N: usize> {

4
i18n.toml Normal file
View File

@ -0,0 +1,4 @@
fallback-language = "en-GB"
[fluent]
assets_dir = "i18n"