Re-started server: A simpler API.

Started: web service API.

Migrated old skeleton codebase.

Upgraded dependencies.

Fortune for datse's current commit: Half curse − 半凶
simple
Avril 3 years ago
parent c491d50315
commit d8a5727d0c
Signed by: flanchan
GPG Key ID: 284488987C31F630

1036
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -21,26 +21,26 @@ client = []
[dependencies]
base64 = "0.13.0"
bitflags = "1.2.1"
bitflags = "1.3.2"
chrono = "0.4.19"
color-eyre = {version = "0.5", default-features=false}
cryptohelpers = { version = "1.7", default-features=false, features = ["async", "sha256", "rsa", "serialise", "aes"] }
futures = "0.3.8"
generational-arena = {version = "0.2.8", features= ["serde"]}
getrandom = "0.2.0"
color-eyre = { version = "0.5.11", default-features = false }
cryptohelpers = { version = "1.8.2", default-features = false, features = ["async", "sha256", "rsa", "serialise", "aes"] }
futures = "0.3.17"
generational-arena = { version = "0.2.8", features = ["serde"] }
getrandom = "0.2.3"
jemallocator = "0.3.2"
lazy_static = "1.4.0"
log = "0.4.11"
log = "0.4.14"
pretty_env_logger = "0.4.0"
rand = "0.7.3"
regex = "1.4.2"
serde = {version = "1.0", features = ["derive"]}
serde_cbor = "0.11.1"
smallmap = {version = "1.2", features= ["serde"]}
rand = "0.8.4"
regex = "1.5.4"
serde = { version = "1.0.130", features = ["derive"] }
serde_cbor = "0.11.2"
smallmap = { version = "1.3.1", features = ["serde"] }
stack-vec = "0.1.0"
tokio = {version = "0.2", features = ["full"]}
uuid = {version = "0.8.1", features = ["v4","serde"]}
warp = "0.2.5"
tokio = { version = "1.11.0", features = ["full"] }
uuid = { version = "0.8.2", features = ["v4","serde"] }
warp = "0.3.1"
[build-dependencies]
rustc_version = "0.2"
rustc_version = "0.4.0"

@ -1,23 +0,0 @@
//! Arg parsing error
use super::*;
use std::{
error,
fmt,
};
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Unknown,
}
impl error::Error for Error{}
impl fmt::Display for Error
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self {
_ => write!(f, "unknown error"),
}
}
}

@ -1,28 +0,0 @@
//! Parse args
use super::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Operation
{
#[cfg(feature="server")] Server(server::Config),
#[cfg(feature="client")] Client(client::Config),
Help,
}
/// Name of the program
pub fn program() -> &'static str
{
lazy_static!{
static ref NAME: String = std::env::args().next().unwrap();
}
&NAME[..]
}
/// Attempt to parse the args
pub fn parse_args() -> impl Future<Output = Result<Operation, Error>>
{
parse::parse(std::env::args().skip(1))
}
mod parse;
mod error;
pub use error::Error;

@ -1,10 +0,0 @@
//! Parse args
use super::*;
pub async fn parse<T, I>(args: I) -> Result<Operation, Error>
where I: IntoIterator<Item = T>,
T: Into<String>
{
let mut args = args.into_iter().map(Into::into);
todo!()
}

@ -1,84 +0,0 @@
//! Caching utilities
use std::mem::{self, MaybeUninit};
use std::error;
use std::borrow::Borrow;
use std::sync::RwLock;
/// A trait for objects that can cache an operation in themselves
pub trait Cache
{
type Cached;
/// Compute the `Cached` value.
fn cache(&self) -> Self::Cached;
}
/// A value that might be cached or not.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum MaybeCached<T: Cache>
{
Uncached(T),
Cached(T::Cached),
}
impl<T: Cache> MaybeCached<T>
{
/// Cache the operation
#[inline] pub fn cache(&mut self)
{
match self {
Self::Uncached(val) => *self = Self::Cached(val.cache()),
_ => (),
}
}
/// Has this value been cached
pub fn is_cached(&self) -> bool
{
if let Self::Cached(_) = &self {
true
} else {
false
}
}
/// Consume into the cached operation
#[inline] pub fn into_cached(mut self) -> Self
{
self.cache();
self
}
/// Compute the operation
#[inline] pub fn compute(self) -> T::Cached
{
match self {
Self::Cached(c) => c,
Self::Uncached(val) => val.cache(),
}
}
}
impl<F, T> Cache for F
where F: Fn() -> T
{
type Cached = T;
#[inline] fn cache(&self) -> Self::Cached
{
self()
}
}
/*
#[derive(Debug)]
pub struct LazyCached<T: Cache + ?Sized>(RwLock<Option<T::Cached>>, T);
impl<T: Cache> LazyCached<T>
{
pub fn new(value: T) -> Self
{
Self(RwLock::new(None), value)
}
}
*/
//TODO:idfk..

@ -1,10 +0,0 @@
//! datse client
use super::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Config
{
}
//TODO

@ -1,203 +0,0 @@
//! Conversion formats
use super::*;
use std::{
ops::Deref,
borrow::Borrow,
fmt,
error,
convert::TryFrom,
};
use regex::{
Regex,
};
//TODO: Mayb use base65536 for URL stuffs? idk..
const BASE64_VALIDATE_RE_STR: &'static str = r#"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"#;
const MOD_BASE64_VALIDATE_RE_STR: &'static str = r#"^(?:[-A-Za-z0-9_]{4})*(?:[-_A-Za-z0-9]{2}~~|[-_A-Za-z0-9]{3}~)?$"#;
lazy_static!{
static ref BASE64_CONV_TABLE: smallmap::Map<char, char> = smallmap![
{'/' => '_'},
{'+' => '-'},
{'=' => '~'},
];
static ref BASE64_CONV_TABLE_REV: smallmap::Map<char, char> = BASE64_CONV_TABLE.clone().reverse();
static ref BASE64_VALIDATE_REGEX: Regex = Regex::new(BASE64_VALIDATE_RE_STR).expect("Failed to compile base64 validation regex");
static ref MOD_BASE64_VALIDATE_REGEX: Regex = Regex::new(MOD_BASE64_VALIDATE_RE_STR).expect("Failed to compile modified base64 validation regex");
}
/// An error in base64 or modified base64 encoding/decoding.
#[derive(Debug)]
pub struct Base64Error<T>(T);
impl<T> Base64Error<T>
{
/// The invalid value of this error
pub fn value(&self) -> &T
{
&self.0
}
/// Consume into the invalid value from this instance
#[inline] pub fn into_inner(self) -> T
{
self.0
}
}
impl<T> error::Error for Base64Error<T>
where T: AsRef<str> + fmt::Debug{}
impl<T> fmt::Display for Base64Error<T>
where T: AsRef<str>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "invalid base64: {:?}", self.0.as_ref())
}
}
/// A string of modified base64, appropriate for URL paths etc.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ModifiedBase64String(String);
impl fmt::Display for ModifiedBase64String
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{}", self.0)
}
}
impl ModifiedBase64String
{
#[inline(always)] fn from_base64_unchecked(string: &str) -> Self
{
Self(conv_str(&BASE64_CONV_TABLE, string).collect())
}
/// Consume into a normal base64 string
pub fn into_base64(self) -> String
{
conv_char_iter(&BASE64_CONV_TABLE_REV, self.0.chars()).collect()
}
/// Consume into the inner modified base64 string
pub fn into_string(self) -> String
{
self.0
}
/// Create an instance from a modified base64 string
pub fn new(from: String) -> Result<Self, Base64Error<String>>
{
if MOD_BASE64_VALIDATE_REGEX.is_match(from.as_str()) {
Ok(Self(from))
} else {
Err(Base64Error(from))
}
}
/// Try to convert a base64 string into a modified base64 string
pub fn try_from_base64<T: AsRef<str>>(base64: T) -> Result<Self, Base64Error<T>>
{
let string = base64.as_ref();
if BASE64_VALIDATE_REGEX.is_match(string) {
Ok(Self::from_base64_unchecked(string))
} else {
Err(Base64Error(base64))
}
}
/// As a string slice
#[inline(always)] pub fn as_str(&self) -> &str
{
self.0.as_str()
}
/// Encode from a slice
pub fn encode(slice: impl AsRef<[u8]>) -> Self
{
Self::from_base64_unchecked(base64::encode(slice.as_ref()).as_str())
}
/// Consume into decoded bytes, write those bytes into the provided buffer
pub fn decode(self, output: &mut [u8]) -> usize
{
base64::decode_config_slice(self.into_base64(), base64::STANDARD, output).expect("modified base64 string contained invalid formatted data")
}
/// Consume into decoded bytes, return those bytes as a new `Vec<u8>`
pub fn decode_new(self) -> Vec<u8>
{
base64::decode(self.into_base64()).expect("modified base64 string contained invalid formatted data")
}
}
impl Deref for ModifiedBase64String
{
type Target = str;
#[inline] fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<str> for ModifiedBase64String
{
#[inline] fn as_ref(&self) -> &str
{
self.as_str()
}
}
impl TryFrom<String> for ModifiedBase64String
{
type Error = Base64Error<String>;
#[inline] fn try_from(from: String) -> Result<Self, Self::Error>
{
Self::try_from_base64(from)
}
}
impl From<ModifiedBase64String> for String
{
#[inline] fn from(from: ModifiedBase64String) -> Self
{
from.into_base64()
}
}
/// Convert this string with a specified char map. Returns a `char` yielding iterator.
#[inline] pub fn conv_str<'a, 'b>(table: &'b smallmap::Map<char, char>, string: &'a (impl AsRef<str> + ?Sized)) -> CharSubstituteIter<'b, std::str::Chars<'a>>
where 'b: 'a
{
conv_char_iter(table, string.as_ref().chars())
}
/// Convert this iterator of chars with this char swapping map
#[inline] pub fn conv_char_iter<T, I>(table: &smallmap::Map<char, char>, iter: I) -> CharSubstituteIter<I::IntoIter, T>
where I: IntoIterator<Item=T>,
T: From<char> + smallmap::Collapse,
char: Borrow<T>
{
iter.replace_chars(table)
}
#[cfg(test)]
mod tests
{
#[test]
fn mod_base64_enc_dec()
{
let mut value = [0u8; 512];
getrandom::getrandom(&mut value[..]).expect("setup failed");
let md = super::ModifiedBase64String::encode(&value[..]);
println!("e-md: {:?}", md);
let ou = md.decode_new();
assert_eq!(&ou[..], &value[..]);
}
}

@ -1,352 +0,0 @@
//! Extensions
use super::*;
use std::{
borrow::{
Borrow, ToOwned,
},
iter,
};
pub trait Tuple2MapExt<T>
{
fn map<F, U>(self, fun: F) -> (U, U)
where F: FnMut(T) -> U;
}
impl<T> Tuple2MapExt<T> for (T,T)
{
fn map<F, U>(self, mut fun: F) -> (U, U)
where F: FnMut(T) -> U
{
(fun(self.0), fun(self.1))
}
}
pub trait JitterExt<T>
{
/// Produce a random value between `self.0` and `self.1` inclusive
fn jitter(self) -> T;
}
impl<T> JitterExt<T> for (T, T)
where T: rand::distributions::uniform::SampleUniform
{
fn jitter(self) -> T
{
util::jitter(self.0, self.1)
}
}
pub trait Unreference<T>
{
fn cloned(self) -> Option<T>;
}
impl<'a, T> Unreference<T> for Option<&'a T>
where T: Clone
{
fn cloned(self) -> Option<T> {
self.map(Clone::clone)
}
}
/// An iterator over `char` that maps certain characters to others
pub struct CharSubstituteIter<'map, I, T= char>
where I: Iterator<Item = T>,
{
iter: I,
map: &'map smallmap::Map<char, char>,
}
impl<'a, I, T> Iterator for CharSubstituteIter<'a, I, T>
where I: Iterator<Item = T>,
T: From<char> + smallmap::Collapse,
char: Borrow<T>
{
type Item = T;
fn next(&mut self) -> Option<Self::Item>
{
self.iter.next()
.map(|item| self.map.get(&item)
.cloned()
.map(T::from)
.unwrap_or(item))
}
#[inline] fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a, I, T> DoubleEndedIterator for CharSubstituteIter<'a, I, T>
where I: Iterator<Item = T> + DoubleEndedIterator,
T: From<char> + smallmap::Collapse,
char: Borrow<T>
{
fn next_back(&mut self) -> Option<Self::Item>
{
self.iter.next_back()
.map(|item| self.map.get(&item)
.cloned()
.map(T::from)
.unwrap_or(item))
}
}
impl<'a, I, T> iter::FusedIterator for CharSubstituteIter<'a, I, T>
where I: Iterator<Item = T> + iter::FusedIterator,
T: From<char> + smallmap::Collapse,
char: Borrow<T>{}
impl<'a, I, T> iter::ExactSizeIterator for CharSubstituteIter<'a, I, T>
where I: Iterator<Item = T> + ExactSizeIterator,
T: From<char> + smallmap::Collapse,
char: Borrow<T>{}
pub trait CharMapExt<T>: Sized + IntoIterator<Item=T>
{
/// Creates an iterator that maps chars over this one
fn replace_chars(self, map: &smallmap::Map<char, char>) -> CharSubstituteIter<'_, Self::IntoIter, T>;
}
impl<S, T> CharMapExt<T> for S
where S: IntoIterator<Item=T>,
T: From<char> + smallmap::Collapse,
char: Borrow<T>
{
#[inline] fn replace_chars(self, map: &smallmap::Map<char, char>) -> CharSubstituteIter<'_, Self::IntoIter, T> {
CharSubstituteIter {
iter: self.into_iter(),
map,
}
}
}
/// The ID type used for backing ID types;
pub type GenericID = uuid::Uuid;
/// Create a type that contains a (globally) unique ID.
#[macro_export] macro_rules! id_type {
($name:ident $(: $doc:literal)?) => ($crate::id_type!{pub(self) $name $(: $doc)?});
($vis:vis $name:ident $(: $doc:literal)?) => {
$(#[doc=$doc])?
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
$vis struct $name($crate::ext::GenericID);
impl $name
{
/// Create a new unique ID.
#[inline(always)] fn id_new() -> Self
{
Self($crate::ext::GenericID::new_v4())
}
/// The generic ID type backing this one
#[inline(always)] fn id_generic(&self) -> &$crate::ext::GenericID
{
&self.0
}
/// Consume into the generic ID
#[inline(always)] fn id_into_generic(self) -> $crate::ext::GenericID
{
self.0
}
/// Create from a generic ID
#[inline(always)] fn id_from_generic(gen: $crate::ext::GenericID) -> Self
{
Self(gen)
}
}
impl ::std::fmt::Display for $name
{
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result
{
use ::std::fmt::Write;
f.write_str(concat!(stringify!($name),"<"))?;
self.0.fmt(f)?;
f.write_str(">")
}
}
}
}
/// Expands to `unreachable_unchecked` in non-debug builds.
///
/// # Safety
/// You must make 100% sure this code path will never be entered, or it will cause undefined behaviour in release builds.
#[macro_export] macro_rules! debug_unreachable {
() => {
if cfg!(debug_assertions) {
#[cold] unreachable!()
} else {
::std::hint::unreachable_unchecked()
}
};
}
/// Dirty debugging macro to get the compiler to print an error message telling you the size of a type.
/// ```
/// check_size!((u8, u8)); // Expected ... found one with *2* elements
/// ```
/// # Size assertions
/// Can also be used to statically assert the size of a type
/// ```
/// # use datse::ext::check_size;
/// check_size!(u16 where == 2; "u16 should be 2 bytes");
/// check_size!(u16 where < 3; "u16 should be lower than 3 bytes");
/// check_size!(u16 where > 1; "u16 should be larger that 1 byte");
/// check_size!(u16 where != 0; "u16 should be larger that 0 bytes");
/// ```
///
/// You can also combine multiple
/// ```
/// # use datse::ext::check_size;
/// check_size!([u8; 512] where {
/// != 10;
/// > 511;
/// < 513;
/// == 512
/// });
/// ```
///
/// This can be used to give you prompts as to when you might want to consider boxing a type.
#[macro_export] macro_rules! check_size {
($t:ty) => {
const _: [(); 0] = [(); ::std::mem::size_of::<$t>()];
};
($t:ty where == $n:literal $(; $msg:literal)?) => {
const _: [(); $n] = [(); ::std::mem::size_of::<$t>()];
};
($t:ty where {$($op:tt $n:literal);+} $(; $msg:literal)?) => {
$(
$crate::static_assert!(::std::mem::size_of::<$t>() $op $n);
)+
};
($t:ty where $op:tt $n:literal $(; $msg:literal)?) => {
$crate::static_assert!(::std::mem::size_of::<$t>() $op $n $(; $msg)?);
};
}
check_size!(u8 where == 1);
check_size!(u16 where > 1);
check_size!([u8; 512] where <= 512);
check_size!([u8; 512] where {
!= 10;
> 511;
< 513;
== 512
});
/// Assert the output of a constant boolean expression is `true` at compile time.
#[macro_export] macro_rules! static_assert {
($val:expr $(; $msg:literal)?) => {
const _: [(); 1] = [(); ($val as bool) as usize];
}
}
/// Assert a trait is object safe. This will produce a compiler error if the trait is not object safe
#[macro_export] macro_rules! assert_object_safe {
($trait:path $(; $msg:literal)?) => {
const _:() = {
#[cold] fn __assert_object_safe() -> !
{
let _: &dyn $trait;
unsafe {
debug_unreachable!()
}
}
};
}
}
assert_object_safe!(AsRef<str>; "object safety assertion test");
static_assert!(1+1==2; "static assertion test");
pub trait UnwrapInfallible<T>
{
fn unwrap_infallible(self) -> T;
}
impl<T> UnwrapInfallible<T> for Result<T, std::convert::Infallible>
{
/// Unwrap with 0 overhead for values that cannot possibly be `Err`.
#[inline(always)] fn unwrap_infallible(self) -> T {
match self {
Ok(v) => v,
#[cold] Err(_) => unsafe { debug_unreachable!() },
}
}
}
#[cfg(nightly)]
impl<T> UnwrapInfallible<T> for Result<T, !>
{
/// Unwrap with 0 overhead for values that cannot possibly be `Err`.
#[inline(always)] fn unwrap_infallible(self) -> T {
match self {
Ok(v) => v,
#[cold] Err(_) => unsafe { debug_unreachable!() },
}
}
}
pub trait UnwrapErrInfallible<T>
{
fn unwrap_err_infallible(self) -> T;
}
impl<T> UnwrapErrInfallible<T> for Result<std::convert::Infallible, T>
{
/// Unwrap with 0 overhead for values that cannot possibly be `Ok`.
#[inline(always)] fn unwrap_err_infallible(self) -> T {
match self {
Err(v) => v,
#[cold] Ok(_) => unsafe { debug_unreachable!() },
}
}
}
#[cfg(nightly)]
impl<T> UnwrapErrInfallible<T> for Result<!, T>
{
/// Unwrap with 0 overhead for values that cannot possibly be `Ok`.
#[inline(always)] fn unwrap_err_infallible(self) -> T {
match self {
Err(v) => v,
#[cold] Ok(_) => unsafe { debug_unreachable!() },
}
}
}
/// Declair a `submodule`.
///
/// Load the private module, and then re-export all its internals.
#[macro_export] macro_rules! submod {
(priv $name:ident $(; $doc:literal)?) => {
$(#[doc=$doc])?
mod $name;
use self::$name::*;
};
($vis:vis $name:ident $(; $doc:literal)?) => {
$(#[doc=$doc])?
mod $name;
$vis use self::$name::*;
};
($name:ident $(; $doc:literal)?) => ($crate::submod!(pub $name $(; $doc)?));
}

@ -39,24 +39,14 @@ fn install() -> eyre::Result<()>
Ok(())
}
mod cache;
mod ext;
use ext::*;
mod util;
mod conv;
mod args;
#[cfg(feature="server")] mod server;
#[cfg(feature="client")] mod client;
//#[cfg(feature="client")] mod client;
#[tokio::main]
async fn main() -> eyre::Result<()> {
install()
.wrap_err(eyre!("Failed to install handlers"))?;
//TODO: Parse args and delegate to client or server
Ok(())
}

@ -1,211 +0,0 @@
//! Data structures for the in-memory map.
use super::*;
use std::{
collections::HashMap,
borrow::Cow,
error,
fmt,
path::PathBuf,
fs,
num::NonZeroI32,
};
use generational_arena::{
Arena, Index,
};
use bitflags::bitflags;
use cryptohelpers::{
rsa::{
RsaPublicKey, RsaPrivateKey,
Signature,
},
aes::{self, AesKey,},
sha256::Sha256Hash,
};
use tokio::io::{
AsyncRead, AsyncWrite,
};
/// An absolute (nested) index
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AbsoluteIndex(Vec<Index>);
id_type!(pub CachedFileId: "The file path is this ID inside the data dir. It may be subject to caching");
/// Possible value types of the data map
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Data
{
Byte(u8),
Char(char),
Bool(bool),
/// Signed integer
SI(i64),
/// Unsigned integer
UI(u64),
/// Floating-point integer
FI(f64),
/// A UTF-8 text string
Text(String),
/// A binary blob
Binary(Vec<u8>),
/// A UTF-8 text string outsourced to a file.
///
/// The file path is this ID inside the data dir. It may be subject to caching later down the line, and will require deleting when an Atom of this type is removed.
/// The files corresponding to these entries should be lazy-loaded (as open `tokio::fs::File`s) into a `DelayQueue` for caching.
TextFile(CachedFileId),
/// A binary blob outsourced to a file.
///
/// The file path is this ID inside the data dir. It may be subject to caching later down the line, and will require deleting when an Atom of this type is removed.
/// The files corresponding to these entries should be lazy-loaded (as open `tokio::fs::File`s) into a `DelayQueue` for caching.
BinaryFile(CachedFileId),
/// A reference index to an item within the same `Datamap` as this one.
RelativeRef(Index),
/// A reference to an item N deep within nested `Map` elements.
///
/// The first `Index` specifies the `Map` data item at the root `Datamap` that contains the next, et cetera. The pointed to value is the last index.
AbsoluteRef(AbsoluteIndex),
/// A list of atoms
List(Vec<Atom>),
/// Another datamap
Map(Datamap),
/// An AES key
AesKey(AesKey),
/// An RSA keypair
RsaKeypair(RsaPrivateKey, RsaPublicKey),
/// An RSA private key
RsaPrivate(RsaPrivateKey),
/// An RSA public key
RsaPublic(RsaPublicKey),
/// A SHA256 hash
Hash(Sha256Hash),
/// A unique ID
Uuid(uuid::Uuid),
/// An RSA signature
Signature(Signature),
/// Nothing
Null,
}
/// An entry that may or may not be encrypted
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MaybeEncrypted
{
/// This is an encrypted serialized `Data`.
Encrypted(Vec<u8>),
/// An unencrypted `Data`
Unencrypted(Data),
}
submod!(encryption; "impl for `MaybeEncrypted`. \
Other `Data` encryption helpers");
bitflags! {
/// And additional metadata for values
#[derive(Serialize, Deserialize)]
struct Tags: u16
{
/// Default
const NONE = 0;
/// This value should be cloned on write unless specified elsewhere.
const COW = 1<<0;
/// This should not show up in searches.
const HIDDEN = 1<<1;
/// Versioning is enabled for this value.
///
/// When it is deleted, it should instead be moved away into a sperate entry.
const VERSIONED = 1<<2;
}
}
/// Information about a map entry.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Info
{
alt_name: Option<(String, String)>,
created: u64,
modified: u64,
tags: Tags,
/// Events to be emitted to `State` when this element does something.
///
/// Can be used for monitoring or logging and such.
hooks: event::Hooks,
owner: Option<Vec<user::EntityID>>, //starts as the user that created (i.e. same as `created_by`), or `None` if ownership is disabled
signed: Option<Vec<Signature>>,
perms: user::Permissions,
created_by: user::UserID,
log: Vec<event::InBandEvent>,
}
/// The root data containing map
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Datamap
{
data: Arena<Atom>,
ident: HashMap<Identifier, Index>,
}
impl PartialEq for Datamap
{
fn eq(&self, other: &Self) -> bool
{
self.ident == other.ident && {
for (v1, v2) in self.ident.iter().map(|(_, v)| (self.data.get(*v), other.data.get(*v)))
{
if v1 != v2 {
return false;
}
}
true
}
}
}
/// A value in a datamap, contains the information about the value and the value itself.
///
/// May also contain previous versions of this atom.
///
/// # Note
/// `Atom` boxes most of itself. It's not needed to box `Atom` itself.
// Box these first two together, since they are hueg.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Atom(Box<(MaybeEncrypted, Info)>, Vec<Atom>);
/// An identifier for an item in a `Datamap`, or an item nested within many `Datamap`s.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Identifier(String);
#[cfg(test)]
mod tests
{
#[test]
fn data_me_sync()
{
let data = super::Data::SI(-120);
let aes = super::aes::AesKey::generate().unwrap();
println!("Data: {:?}", data);
let enc = super::MaybeEncrypted::new_encrypted(data.clone(), &aes).expect("Couldn't encrypt");
println!("M/Enc: {:?}", enc);
let dec = enc.clone().into_unencrypted(Some(&aes)).expect("Couldn't decrypt");
println!("M/Dec: {:?}", dec);
let out = dec.into_data(Some(&aes)).unwrap();
assert_eq!(data, out);
}
}

@ -1,181 +0,0 @@
use super::*;
#[non_exhaustive]
#[derive(Debug)]
pub(super) enum EncryptedEntryErrorKind
{
KeyNeeded,
Decrypt(aes::Error),
Deserialize(serde_cbor::Error),
Encrypt(aes::Error),
Serialize(serde_cbor::Error),
}
/// Error type returned when performing encryption/decryption operations on `Data` through `MaybeEncrypted`.
#[non_exhaustive]
#[derive(Debug)]
pub struct EncryptedEntryError(Box<EncryptedEntryErrorKind>);
impl EncryptedEntryError
{
/// Consume into a nicely readable `eyre::Report`
pub fn report(self) -> eyre::Report
{
let (whe, sug) = match self.0.as_ref() {
EncryptedEntryErrorKind::Serialize(_) => ("Object serialisation", "Bad data?"),
EncryptedEntryErrorKind::Deserialize(_) => ("Object deserialisation", "Corrupted data?"),
EncryptedEntryErrorKind::Decrypt(_) => ("Data decryption", "Bad key?"),
EncryptedEntryErrorKind::Encrypt(_) => ("Data encryption" ,"Bad key?"),
EncryptedEntryErrorKind::KeyNeeded => return eyre::Report::from(self)
.with_suggestion(|| "Try providing a key"),
};
Err::<std::convert::Infallible, _>(self)
.with_section(|| whe.header("In operation"))
.with_warning(|| sug)
.unwrap_err()
}
}
impl error::Error for EncryptedEntryError
{
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
Some(match self.0.as_ref() {
EncryptedEntryErrorKind::Decrypt(d) => d,
EncryptedEntryErrorKind::Deserialize(d) => d,
EncryptedEntryErrorKind::Encrypt(d) => d,
EncryptedEntryErrorKind::Serialize(d) => d,
_ => return None,
})
}
}
impl fmt::Display for EncryptedEntryError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self.0.as_ref() {
EncryptedEntryErrorKind::KeyNeeded => write!(f, "this entry needed a decryption key but none was provided"),
EncryptedEntryErrorKind::Decrypt(_) => write!(f, "decryption failed"),
EncryptedEntryErrorKind::Deserialize(_) => write!(f, "deserialisation failed"),
EncryptedEntryErrorKind::Encrypt(_) => write!(f, "encryption failed"),
EncryptedEntryErrorKind::Serialize(_) => write!(f, "serialisation failed"),
}
}
}
impl From<EncryptedEntryErrorKind> for EncryptedEntryError
{
#[inline] fn from(from: EncryptedEntryErrorKind) -> Self
{
Self(Box::new(from))
}
}
/// Encrypt a `Data` entry into this buffer
pub(super) async fn encrypt_data_entry<T>(output: &mut T, data: &Data, key: &AesKey) -> Result<usize, EncryptedEntryError>
where T: ?Sized + AsyncWrite + Unpin
{
let bytes = serde_cbor::to_vec(data).map_err(EncryptedEntryErrorKind::Serialize)?;
Ok(aes::encrypt_stream(key, &mut &bytes[..], output).await.map_err(EncryptedEntryErrorKind::Encrypt)?)
}
/// Decrypt an encrypted `Data` entry
pub(super) async fn decrypt_data_entry<T: ?Sized>(input: &mut T, key: &AesKey, init_buf: Option<Vec<u8>>) -> Result<Data, EncryptedEntryError>
where T: AsyncRead + Unpin
{
let mut output = init_buf.unwrap_or_default();
aes::decrypt_stream(key, input, &mut output).await.map_err(EncryptedEntryErrorKind::Decrypt)?;
Ok(serde_cbor::from_slice(&output[..]).map_err(EncryptedEntryErrorKind::Deserialize)?)
}
impl MaybeEncrypted
{
/// Create a new non-encrypted entry
#[inline(always)] pub const fn new_raw(data: Data) -> Self
{
Self::Unencrypted(data)
}
/// Create a new encrypted entry
#[inline] pub fn new_encrypted(data: Data, encrypt: &AesKey) -> Result<Self, EncryptedEntryError>
{
let mut buffer = Vec::new();
encrypt_data_entry(&mut buffer, &data, encrypt).now_or_never().unwrap()?;
Ok(Self::Encrypted(buffer))
}
/// Is this data entry encrypted
#[inline] pub fn is_encrypted(&self) -> bool
{
if let MaybeEncrypted::Encrypted(_) = &self {
true
} else {
false
}
}
/// Consume into a decrypted data instance
#[inline] pub fn into_unencrypted(self, key: Option<&AesKey>) -> Result<Self, EncryptedEntryError>
{
Ok(Self::Unencrypted(self.into_data(key)?))
}
/// Make this entry unencrypted
pub fn make_unencrypted(&mut self, key: Option<&AesKey>) -> Result<(), EncryptedEntryError>
{
match self {
MaybeEncrypted::Encrypted(bytes) => {
*self = MaybeEncrypted::Unencrypted(decrypt_data_entry(&mut &bytes[..], key.ok_or(EncryptedEntryErrorKind::KeyNeeded)?, Some(Vec::with_capacity(bytes.len()))).now_or_never().unwrap()?);
},
_ => (),
}
Ok(())
}
/// Make this entry encrypted
pub fn make_encrypted(&mut self, key: Option<&AesKey>) -> Result<(), EncryptedEntryError>
{
match self {
MaybeEncrypted::Unencrypted(data) => {
let mut buffer = Vec::new();
encrypt_data_entry(&mut buffer, data, key.ok_or(EncryptedEntryErrorKind::KeyNeeded)?).now_or_never().unwrap()?;
*self = Self::Encrypted(buffer);
},
_ => (),
}
Ok(())
}
/// Consume into an encrypted data instance
pub fn into_encrypted(self, key: Option<&AesKey>) -> Result<Self, EncryptedEntryError>
{
match self {
MaybeEncrypted::Unencrypted(data) => Self::new_encrypted(data, key.ok_or(EncryptedEntryErrorKind::KeyNeeded)?),
_ => Ok(self),
}
}
/// Attempt to get the `Data`, decrypting it if needed.
pub fn get_data<'a>(&'a self, key: Option<&AesKey>) -> Result<Cow<'a, Data>, EncryptedEntryError>
{
Ok(match self {
Self::Unencrypted(data) => Cow::Borrowed(&data),
Self::Encrypted(bytes) => {
// decrypt
Cow::Owned(decrypt_data_entry(&mut &bytes[..], key.ok_or(EncryptedEntryErrorKind::KeyNeeded)?, Some(Vec::with_capacity(bytes.len()))).now_or_never().unwrap()?)
},
})
}
/// Consume into the data object, decrypting it if needed.
pub fn into_data(self, key: Option<&AesKey>) -> Result<Data, EncryptedEntryError>
{
Ok(match self {
Self::Encrypted(bytes) => {
decrypt_data_entry(&mut &bytes[..], key.ok_or(EncryptedEntryErrorKind::KeyNeeded)?, Some(Vec::with_capacity(bytes.len()))).now_or_never().unwrap()?
},
Self::Unencrypted(data) => data,
})
}
}

@ -1,125 +0,0 @@
//! When events happen to datamaps
use super::*;
use bitflags::bitflags;
/// What happened in the in-band event
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum InBandEventKind
{
Created,
Modified,
Deleted,
Renamed(String), // Previous name
Cloned(data::AbsoluteIndex), // Index of the new item
Moved,
Comment(String),
}
/// An event that happened to a data entry that is then stored in its metadata.
///
/// # Note
/// Not to be confused with `HookEvent`s
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InBandEvent
{
who: Option<user::UserID>,
what: InBandEventKind,
when: u64,
signed: Option<cryptohelpers::rsa::Signature>,
}
bitflags! {
/// Kind of events that should emmit for a hook
#[derive(Serialize, Deserialize)]
struct HookMask: u16
{
/// Filter all events. None are emiited.
const NONE = 0;
/// Mask any event, any kind is emitted.
const ANY = !0;
/// Emmit read events.
const READ = 1<<0;
/// Emmit write events.
const WRITE = 1<<1;
/// Emmit delete events.
const DELETE = 1<<2;
/// Propagate events from children of `Map` or `List` datas
const CHILD = 1<<3;
/// Poke events are opaque events that are fired manually.
const POKE = 1<<4;
}
}
impl Default for HookMask
{
#[inline(always)]
fn default() -> Self
{
Self::NONE
}
}
id_type!(pub HookID: "The ID of a hook, passed with the event when the hook is fired");
/// Fire events when something happens to this value.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Hooks
{
filter: HookMask,
id: HookID,
}
impl Default for Hooks
{
#[inline]
fn default() -> Self
{
Self {
id: HookID::id_new(), // generate an id now, in case hooks are added later
filter: Default::default(), //default is to emit nothing ever
}
}
}
/// An event emitted from a matched `Hook`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum HookEventKind
{
/// An opaque event type that can only be fired manually.
Poke,
Read,
Write,
Delete,
/// An event propagated from a child of this element.
///
/// This only can happen with `Map` or `List` data kinds.
Child(Box<HookEvent>),
}
impl Default for HookEventKind
{
#[inline]
fn default() -> Self
{
Self::Poke
}
}
/// An event emitted from a element value's `Hooks` object.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct HookEvent
{
kind: HookEventKind,
/// Corresponds to the `id` field of the `Hooks` object this event was emitted from.
id: HookID,
}

@ -1,18 +1,33 @@
//! Server for datse
use super::*;
/// Config used to create `Settings`
#[derive(Debug, PartialEq, Eq, Clone)]
/// Configuration for the server.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Config
{
pub trust_x_forwarded_for: bool,
}
mod event;
mod user;
mod data;
mod state;
impl Config
{
/// The default configuration
pub const DEFAULT: Self = Self::_default();
#[inline(always)] const fn _default() -> Self
{
Self {
trust_x_forwarded_for: false,
}
}
}
impl Default for Config
{
#[inline]
fn default() -> Self
{
Self::_default()
}
}
#[cfg(feature="server-http")] pub mod web;
#[cfg(feature="server-tcp")] pub mod tcp;
pub mod web;

@ -1,10 +0,0 @@
//! Server state
use super::*;
/// Contains the state of the whole program
#[derive(Debug)]
pub struct ServerState
{
root: data::Datamap,
userspace: user::Userspace,
}

@ -1,4 +0,0 @@
//! datse server over TCP
use super::*;
//TODO: Depends on yet-to-be-written rust TCP encnet RSA-AES encryption wrapper

@ -1,375 +0,0 @@
//! Information about users, perms, etc
use super::*;
use std::collections::HashMap;
use std::borrow::Cow;
use std::ops::Deref;
use bitflags::bitflags;
id_type!(pub UserID: "A unique user ID");
id_type!(pub GroupID: "A user group ID");
//TODO: `User` and `Group` Builder types. Other ways of mutating them? idk yet
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct User
{
id: UserID,
name: String,
hidden: bool,
desc: Option<String>,
comment: Option<String>,
/// Corresponds to a Unix `superuser`.
///
/// All permissions checks are bypassed for a user with this set.
is_god: bool,
groups: Vec<GroupID>,
}
submod!(user; "impls for `User`.");
//TODO: Iterator over all `Group`s a user is a part of (i.e. each group they're in's deduped & flattened inheritance graph)
/// A reference to a user in a userspace from their ID.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserRef<'a>(UserID, &'a Userspace);
impl<'a> UserRef<'a>
{
/// The ID of this user reference
#[inline] pub fn id(&self) -> &UserID
{
&self.0
}
/// The userspace of this user
#[inline] pub fn space(&self) -> &Userspace
{
self.1
}
/// # Note
/// This is for testing purposes only, and should never return `None`.
#[inline(always)] fn try_user(&self) -> Option<&'a User>
{
self.1.users.get(&self.0)
}
/// The user this ID is referring to
#[inline] pub fn user(&self) -> &User
{
self.try_user().unwrap()
}
/// Consume into a reference to the user with the lifetime of the `Userspace` containing it.
#[inline] pub fn into_user(self) -> &'a User
{
self.try_user().unwrap()
}
}
impl<'a> Deref for UserRef<'a>
{
type Target = User;
fn deref(&self) -> &Self::Target {
self.user()
}
}
/// A reference to a group in a userspace from their group ID.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GroupRef<'a>(GroupID, &'a Userspace);
impl<'a> GroupRef<'a>
{
/// The ID of this group reference
#[inline] pub fn id(&self) -> &GroupID
{
&self.0
}
/// The userspace of this group ref
#[inline] pub fn space(&self) -> &Userspace
{
self.1
}
/// # Note
/// This is for testing purposes only, and should never return `None`.
#[inline(always)] fn try_group(&self) -> Option<&'a Group>
{
self.1.groups.get(&self.0)
}
/// The group this ID is referring to
#[inline] pub fn group(&self) -> &Group
{
self.try_group().unwrap()
}
/// Consume into a reference to the group with the lifetime of the `Userspace` containing it.
#[inline] pub fn into_group(self) -> &'a Group
{
self.try_group().unwrap()
}
}
impl<'a> Deref for GroupRef<'a>
{
type Target = Group;
fn deref(&self) -> &Self::Target {
self.group()
}
}
/// A group is a way of setting permissions for a whole set of users.
///
/// Users have groups, not the other way around.
/// A user can have multiple groups or none.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Group
{
id: GroupID,
hidden: bool,
/// Corresponds to a Unix `superuser`.
///
/// All permissions checks are bypassed for a user in a group with this set.
is_god: bool,
name: String,
desc: Option<String>,
comment: Option<String>,
inherits: Option<Vec<GroupID>>,
}
submod!(group; "impls for `Group` related things");
pub trait AsEntityId
{
fn entity_id(&self) -> Cow<'_, EntityID>;
}
assert_object_safe!(AsEntityId);
impl AsEntityId for EntityID
{
#[inline(always)] fn entity_id(&self) -> Cow<'_, EntityID> {
Cow::Borrowed(self)
}
}
impl AsEntityId for UserID
{
fn entity_id(&self) -> Cow<'_, EntityID> {
Cow::Owned(EntityID::User(self.clone()))
}
}
impl AsEntityId for GroupID
{
fn entity_id(&self) -> Cow<'_, EntityID> {
Cow::Owned(EntityID::Group(self.clone()))
}
}
impl<'a, T> AsEntityId for &'a T
where T: AsEntityId
{
#[inline(always)] fn entity_id(&self) -> Cow<'_, EntityID> {
T::entity_id(self)
}
}
/// A trait for items that can have permissions bits set for them.
/// Users and groups implement this.
pub trait Entity
{
fn generic_id(&self) -> &GenericID;
fn name(&self) -> &str;
fn desc(&self) -> Option<&str>;
fn comment(&self) -> Option<&str>;
fn superuser(&self) -> bool;
/// # Note
/// This does not flatten inherited groups, that needs to be performed later
fn groups(&self) -> &[GroupID];
}
assert_object_safe!(Entity);
impl Entity for User
{
#[inline] fn generic_id(&self) -> &GenericID
{
&self.id.0
}
#[inline] fn name(&self) -> &str
{
&self.name[..]
}
#[inline] fn desc(&self) -> Option<&str>
{
self.desc.as_ref().map(String::as_str)
}
#[inline] fn comment(&self) -> Option<&str>
{
self.comment.as_ref().map(String::as_str)
}
#[inline] fn superuser(&self) -> bool
{
self.is_god
}
/// # Note
/// This does not flatten inherited groups, that needs to be performed later
#[inline] fn groups(&self) -> &[GroupID]
{
&self.groups[..]
}
}
impl Entity for Group
{
#[inline] fn generic_id(&self) -> &GenericID
{
&self.id.0
}
#[inline] fn name(&self) -> &str
{
&self.name[..]
}
#[inline] fn desc(&self) -> Option<&str>
{
self.desc.as_ref().map(String::as_str)
}
#[inline] fn comment(&self) -> Option<&str>
{
self.comment.as_ref().map(String::as_str)
}
#[inline] fn superuser(&self) -> bool
{
self.is_god
}
/// # Note
/// This does not flatten inherited groups, that needs to be performed later
#[inline] fn groups(&self) -> &[GroupID]
{
self.inherits.as_ref().map(Vec::as_slice).unwrap_or(&[])
}
}
/// Either a single user or a group of users
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum EntityID
{
User(UserID),
Group(GroupID),
}
impl UserID
{
/// Attempt to find this user in this userspace
pub fn find_in_space<'a>(&self, space: &'a Userspace) -> Option<&'a User>
{
space.users.get(self)
}
/// Attempt to find a mutable reference to this user in this userspace
pub fn find_in_space_mut<'a>(&self, space: &'a mut Userspace) -> Option<&'a mut User>
{
space.users.get_mut(self)
}
}
impl GroupID
{
/// Attempt to find this group in this userspace
pub fn find_in_space<'a>(&self, space: &'a Userspace) -> Option<&'a Group>
{
space.groups.get(self)
}
/// Attempt to find a mutable reference to this group in this userspace
pub fn find_in_space_mut<'a>(&self, space: &'a mut Userspace) -> Option<&'a mut Group>
{
space.groups.get_mut(self)
}
}
impl EntityID
{
/// Attempt to find the entity that implements this ID in this space
pub fn find_in_space<'a>(&self, space: &'a Userspace) -> Option<&'a (dyn Entity + 'static)>
{
match self {
Self::User(id) => space.users.get(id).map(|x| x as &dyn Entity),
Self::Group(id) => space.groups.get(id).map(|x| x as &dyn Entity),
}
}
}
bitflags! {
/// A permission a user or group has for a data item.
#[derive(Serialize, Deserialize)]
struct Permission: u16 {
/// Cannot read or write the item.
///
/// However they can see it, unless it is marked with tag attr "hidden".
const NONE = 0;
/// Can read from the item
const READ = 1;
/// Can write to the item
///
/// # Note
/// This does not imply `READ`.
const WRITE = 2;
/// Full access
const FULL = Self::READ.bits | Self::WRITE.bits;
}
}
impl Default for Permission
{
#[inline(always)]
fn default() -> Self
{
Self::READ
}
}
/// A set of permissions informations for users and/or groups
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Permissions
{
blanket: Permission,
spec: smallmap::Map<EntityID, Permission>,
}
/// Contains all users and groups
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Userspace
{
// use maps here for easier lookup, despite duplicating ID data
users: HashMap<UserID, User>,
groups: HashMap<GroupID, Group>,
}
submod!(userspace; "impl for `Userspace`");

@ -1,126 +0,0 @@
//! Group specific impls
use super::*;
use std::collections::{
VecDeque,
HashSet, hash_set,
};
impl Group
{
/// Is this group hidden from searches?
#[inline(always)] pub fn is_hidden(&self) -> bool
{
self.hidden
}
/// Set if this group should be hidden from searches.
#[inline(always)] pub fn set_hidden(&mut self, hide: bool)
{
self.hidden = hide;
}
/// The flattened inheritance graph of this `Group` within this `Userspace`.
pub fn inherits_from<'g, 'u>(&'g self, space: &'u Userspace) -> GroupInheritanceIter<'u>
where 'g: 'u
{
GroupInheritanceIter {
group: self,
space,
level: self.inherits.clone().unwrap_or_default().into(),
done: HashSet::new(),
is_cyclic: false,
cyclic_refs: Default::default(),
}
}
}
/// An iterator over a `Group`'s entire inheritance graph.
#[derive(Debug, Clone)]
pub struct GroupInheritanceIter<'u>
{
group: &'u Group,
space: &'u Userspace,
level: VecDeque<GroupID>,
done: HashSet<GroupID>,
is_cyclic: bool,
cyclic_refs: HashSet<&'u GroupID>,
}
impl<'a> GroupInheritanceIter<'a>
{
/// The group this iterator is working for
#[inline] pub fn base_group(&self) -> &Group
{
self.group
}
/// The userspace this iterator is searching in
#[inline] pub fn userspace(&self) -> &Userspace
{
self.space
}
/// Does this inheritance graph contain cyclic references so far?
#[inline] pub fn contains_cyclic_references(&self) -> bool
{
self.is_cyclic
}
/// The cyclic references found by this iterator so far (if any).
#[inline] pub fn cyclic_refs(&self) -> hash_set::Iter<'_, &'a GroupID>
{
self.cyclic_refs.iter()
}
/// All group IDs that have been processed by the iterator so far
#[inline] pub fn processed_group_ids(&self) -> hash_set::Iter<'_, GroupID>
{
self.done.iter()
}
}
impl<'u> Iterator for GroupInheritanceIter<'u>
{
type Item = &'u Group;
fn next(&mut self) -> Option<Self::Item>
{
match self.level
.pop_front()
.map(|id| (self.space.groups.get(&id),
self.done.insert(id)))
{
#[cold] Some((None, _)) => panic!("Group inheritance graph for group ID {:?} contained invalid ID not found within this userspace", self.group.id),
Some((Some(group), false)) => {
// We've already processed this ID, it is a cyclic reference.
// Ignore it and continue
if cfg!(debug_assertions) && !self.is_cyclic {
warn!("Cyclic reference found for group ID {:?} while calculating the inheritance graph of group {:?}", group.id, self.group.id);
}
self.is_cyclic = true;
self.cyclic_refs.insert(&group.id);
self.next()
},
Some((Some(group), _)) => {
if let Some(high) = &group.inherits {
self.level.reserve(high.len());
for high in high.iter().cloned().rev() {
self.level.push_front(high);
}
}
Some(group)
},
_ => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.level.len() {
0 => (0, Some(0)),
n => (n, None),
}
}
}
impl<'a> std::iter::FusedIterator for GroupInheritanceIter<'a>{}

@ -1,114 +0,0 @@
//! impls for `User`
use super::*;
use std::borrow::Cow;
use std::iter;
/// A container of `Group` references in a `Userspace` from `GroupID`s
#[derive(Debug, Clone)]
pub struct Groups<'u>(usize, Cow<'u, [GroupID]>, &'u Userspace);
/// An iterator over all groups a user is a part of.
#[derive(Debug, Clone)]
pub struct AllGroups<'u>(Vec<(Option<&'u Group>, GroupInheritanceIter<'u>)>);
impl User
{
/// All groups the user is explicitly a part of
pub fn groups_explicit<'a, 'u>(&'a self, space: &'u Userspace) -> Groups<'u>
where 'a: 'u
{
Groups(0, Cow::Borrowed(&self.groups[..]), space)
}
/// All groups the user is a part of, including inherited groups and implcit (todo) ones
pub fn all_groups<'a, 'u>(&'a self, space: &'u Userspace) -> AllGroups<'u>
where 'a: 'u
{
AllGroups(self.groups_explicit(space).map(|group| (Some(group), group.inherits_from(space))).rev().collect())
}
}
impl<'u> Groups<'u>
{
/// The group IDs remaining in this iterator.
pub fn ids(&self) -> &[GroupID]
{
&self.1[self.0..]
}
/// The userspace for this iterator.
pub fn space(&self) -> &Userspace
{
&self.2
}
/// Copy as a reference with a lower lifetime.
pub fn copied<'a>(&'a self) -> Groups<'a>
{
Groups(self.0, Cow::Borrowed(&self.1[..]), self.2)
}
}
impl<'u> Iterator for Groups<'u>
{
type Item = &'u Group;
fn next(&mut self) -> Option<Self::Item>
{
(if self.0>=self.1.len() {
None
} else {
Some(self.2.groups.get(&self.1[self.0]).expect("Groups contained invalid group ID for its userspace"))
}, self.0+=1).0
}
#[inline] fn nth(&mut self, n: usize) -> Option<Self::Item>
{
if (..self.len()).contains(&n) {
Some(self.2.groups.get(&self.1[n]).expect("Groups contained invalid group ID for its userspace"))
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let v = self.1.len() - self.0;
(v, Some(v))
}
}
impl<'u> iter::DoubleEndedIterator for Groups<'u>
{
fn next_back(&mut self) -> Option<Self::Item>
{
if self.0 == 0 || self.0 >= self.1.len() {
self.0 = self.1.len()-1;
} else {
self.0-=1;
}
debug_assert!(self.0<self.1.len(), "bad DEI impl");
Some(self.2.groups.get(&self.1[self.0]).expect("Groups contained invalid group ID for its userspace"))
}
}
impl<'u> iter::ExactSizeIterator for Groups<'u>{}
impl<'u> iter::FusedIterator for Groups<'u>{}
impl<'u> Iterator for AllGroups<'u>
{
type Item = &'u Group;
fn next(&mut self) -> Option<Self::Item>
{
match self.0.last_mut()
{
Some((mut v @ Some(_), _)) => v.take(),
Some((None, iter)) => {
if let Some(igroup) = iter.next() {
Some(igroup)
} else {
self.0.pop();
self.next()
}
},
_ => None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.0.iter().map(|(g, i)| (g.is_some() as usize) + i.size_hint().0).sum(), None)
}
}
impl<'u> iter::FusedIterator for AllGroups<'u>{}

@ -1,143 +0,0 @@
use super::*;
use std::collections::hash_map;
use std::iter;
impl Userspace
{
/// Is this `User` or `Group` ID present in this space?
pub fn contains_id(&self, ent: &impl AsEntityId) -> bool
{
ent.entity_id().find_in_space(self).is_some()
}
/// Get a user reference from their ID
pub fn user_ref(&self, id: UserID) -> Option<UserRef<'_>>
{
if self.users.contains_key(&id) {
Some(UserRef(id, &self))
} else {
None
}
}
/// Get a group reference from their ID
pub fn group_ref(&self, id: GroupID) -> Option<GroupRef<'_>>
{
if self.groups.contains_key(&id) {
Some(GroupRef(id, &self))
} else {
None
}
}
/// An iterator over the users in this instance
pub fn users(&self) -> UserIter<'_>
{
UserIter(self.users.iter())
}
/// A mutable iterator over the users in this instance
pub fn users_mut(&mut self) -> UserIterMut<'_>
{
UserIterMut(self.users.iter_mut())
}
/// An iterator over the groups in this instance
pub fn groups(&self) -> GroupIter<'_>
{
GroupIter(self.groups.iter())
}
/// A mutable iterator over the groups in this instance
pub fn groups_mut(&mut self) -> GroupIterMut<'_>
{
GroupIterMut(self.groups.iter_mut())
}
/// Number of users
pub fn users_len(&self) -> usize
{
self.users.len()
}
/// Number of groups
pub fn groups_len(&self) -> usize
{
self.groups.len()
}
}
/// Iterator over users
#[derive(Debug, Clone)]
pub struct UserIter<'a>(hash_map::Iter<'a, UserID, User>);
impl<'a> Iterator for UserIter<'a>
{
type Item = &'a User;
fn next(&mut self) -> Option<Self::Item>
{
self.0.next().map(|x| x.1)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> iter::FusedIterator for UserIter<'a>{}
impl<'a> iter::ExactSizeIterator for UserIter<'a>{}
/// Mutable iterator over users
#[derive(Debug)]
pub struct UserIterMut<'a>(hash_map::IterMut<'a, UserID, User>);
impl<'a> Iterator for UserIterMut<'a>
{
type Item = &'a mut User;
fn next(&mut self) -> Option<Self::Item>
{
self.0.next().map(|x| x.1)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> iter::FusedIterator for UserIterMut<'a>{}
impl<'a> iter::ExactSizeIterator for UserIterMut<'a>{}
/// Iterator over groups in a `Userspace`
#[derive(Debug, Clone)]
pub struct GroupIter<'a>(hash_map::Iter<'a, GroupID, Group>);
impl<'a> Iterator for GroupIter<'a>
{
type Item = &'a Group;
fn next(&mut self) -> Option<Self::Item>
{
self.0.next().map(|x| x.1)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> iter::FusedIterator for GroupIter<'a>{}
impl<'a> iter::ExactSizeIterator for GroupIter<'a>{}
/// Mutable iterator over groups in a `Userspace`
#[derive(Debug)]
pub struct GroupIterMut<'a>(hash_map::IterMut<'a, GroupID, Group>);
impl<'a> Iterator for GroupIterMut<'a>
{
type Item = &'a mut Group;
fn next(&mut self) -> Option<Self::Item>
{
self.0.next().map(|x| x.1)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> iter::FusedIterator for GroupIterMut<'a>{}
impl<'a> iter::ExactSizeIterator for GroupIterMut<'a>{}

@ -1,180 +0,0 @@
//! Authentication
use super::*;
use tokio::time;
use std::{error, fmt};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Sha256Hash(pub sha256::Sha256Hash);
type RsaSignature = rsa::Signature;
#[derive(Debug)]
pub struct DecodeTokenError;
impl str::FromStr for Sha256Hash
{
type Err = DecodeTokenError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
conv::ModifiedBase64String::try_from_base64(s).map_err(|_| DecodeTokenError).and_then(|md| {
let mut output =sha256::Sha256Hash::default();
if md.decode(output.as_mut()) == sha256::SIZE {
Ok(Self(output))
} else {
Err(DecodeTokenError)
}
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthRequest
{
pub id: Uuid,
sign_this: [u8; 32],
salt: [u8; 16],
passwd_is_allowed: bool,
ttl_ms: u64
}
impl AuthRequest
{
pub fn hash_password(&self, _state: &State, passwd: &str) -> sha256::Sha256Hash
{
// NOTE: _state will be used when we have a 2nd global salt as well, for now, ignore it.
sha256::compute_slices([passwd.as_bytes(), &self.salt[..]].iter())
}
}
impl AuthRequest
{
/// The TTL for this auth request
pub fn ttl(&self) -> time::Duration
{
time::Duration::from_millis(self.ttl_ms)
}
/// Create a new auth request
pub fn new(cfg: &settings::Settings) -> Self
{
let mut empty = Self {
id: Uuid::new_v4(),
sign_this: [0; 32],
salt: [0;16],
passwd_is_allowed: cfg.allow_passwd_auth,
ttl_ms: cfg.auth_req_ttl_millis.jitter(),
};
getrandom(&mut empty.sign_this[..]).expect("fatal rng");
getrandom(&mut empty.salt[..]).expect("fatal rng");
empty
}
}
pub async fn auth_req(who: source::IpAddr, state: Arc<State>) -> Result<AuthRequest, Infallible>
{
let req = AuthRequest::new(state.cfg());
trace!("{:?} auth req", who);
// Add `req` into `state` auth hashmap (`req.id` is key) for verification.
// Use `DelayQueue` to remove `req.id` from the hashmap after `ttl` expires.
{
let mut auth = state.auth_tokens().await;
auth.insert_req(req.clone());
}
Ok(req)
}
async fn real_auth_key(state: Arc<State>, req_id: Uuid, sigs: impl IntoIterator<Item=RsaSignature>) -> Result<(), AuthError>
{
Ok(())
}
pub async fn auth_key(who: source::IpAddr, state: Arc<State>, req_id: Uuid, num: usize, body: Bytes) -> Result<(), warp::Rejection>
{
trace!("{:?} auth resp key <{}>:{}", who, req_id, num);
//TODO: Read keys from body, pass to `real_auth_key`.
todo!()
}
async fn real_auth_pass(state: Arc<State>, req_id: Uuid, passhash: sha256::Sha256Hash) -> Result<(), AuthError>
{
let req = {
state.auth_tokens().await.handle_req(req_id)?
};
if !req.passwd_is_allowed {
return Err(AuthError::Method);
}
//TODO: Grab valid password hash from `State` and compare
//TODO: Generate real authoriseation token that maps to whichever user was authorised, insert into state with a TTL that gets refreshed when the token is used.
Ok(())
}
pub async fn auth_pass(who: source::IpAddr, state: Arc<State>, req_id: Uuid, passhash: sha256::Sha256Hash) -> Result<(), warp::Rejection>
{
trace!("{:?} auth resp pass <{}>: \"{}\"", who, req_id, passhash);
real_auth_pass(state, req_id, passhash).await.map_err(warp::reject::custom)
}
#[derive(Debug)]
pub enum AuthError
{
Id,
Hash,
Sig,
Method,
Internal,
}
impl AuthError
{
/// A warp recovery filter for auth errors
pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rejection>
{
use warp::http::StatusCode;
if let Some(this) = err.find::<Self>() {
let code = match this {
Self::Id
=> return Err(warp::reject::not_found()),
Self::Hash
| Self::Sig
=> StatusCode::FORBIDDEN,
Self::Method
=> StatusCode::METHOD_NOT_ALLOWED,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
Ok(warp::reply::with_status(format!("auth failed: {}", this), code))
} else {
Err(err)
}
}
}
impl error::Error for AuthError{}
impl warp::reject::Reject for AuthError{}
impl fmt::Display for AuthError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self
{
Self::Id => write!(f, "invalid response id"),
Self::Hash => write!(f, "no matching hash"),
Self::Sig => write!(f, "no matching signature"),
Self::Method => write!(f, "auth method not allowed"),
_ => write!(f, "internal error"),
}
}
}
impl From<state::AuthCacheError> for AuthError
{
fn from(_: state::AuthCacheError) -> Self
{
Self::Id
}
}

@ -21,90 +21,87 @@ use cryptohelpers::{
rsa,
};
use state::State;
use uuid::Uuid;
use server::state::ServerState;
use settings::Settings;
/// Web server config
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config
{
}
pub mod settings;
mod session;
mod state;
mod source;
mod forwarded_list;
mod auth;
/// Main entry point for web server
pub async fn main(state: ServerState, cfg: Settings) -> eyre::Result<()>
pub async fn serve(cfg: Config) -> eyre::Result<()>
{
let state = Arc::new(State::new(state, cfg.clone()));
let state = warp::any().map(move || state.clone());
// Extract the client IP or fail with custom rejection
// Filter: Extract the client IP from the remote address of the connection of the X-Forwarded-For header if it is trusted in `cfg`.
let client_ip = warp::addr::remote()
.and(warp::header("X-Forwarded-For"))
.map(source::extract(cfg.trust_x_forwarded_for))
.and_then(|req: Result<std::net::IpAddr, _>| async move {req.map_err(warp::reject::custom)});
// /auth/req - Request an auth ID and information about how to respond (data to sign / password salt / what is supported, etc)
// /auth/resp/<req_id>/pw/<passwd hash> - Respond to an auth ID with a hashed password, salted with the salt obtained from the req call.
// /auth/resp/<req_id>/si[/<num of sigs in body>] - Respond to an auth ID with one or more signatures of the data to be signed obtained in the auth request. If no number is provided, 1 is assumed.
let auth = {
let req = warp::path("req")
.and(client_ip.clone()).and(state.clone())
.and_then(auth::auth_req);
let resp = {
let resp_auth_with_state = warp::post()
.and(client_ip.clone()).and(state.clone())
.and(warp::path::param().map(|req_id: uuid::Uuid| req_id));
let resp_auth_key = resp_auth_with_state.clone()
.and(warp::path("si")
.and(warp::path::param().map(|num: usize| std::cmp::min(std::cmp::max(1,
num),
cfg.max_key_sigs_per_auth_response))
.or(warp::path::end().map(|| 1usize)).unify()))
.and(warp::body::content_length_limit(cfg.max_body_len.0))
.and(warp::body::bytes())
.and_then(auth::auth_key);
// -- Paths --
let resp_auth_pass = {
let pw_path = resp_auth_with_state
.and(warp::path("pw"));
if cfg.allow_passwd_auth {
pw_path.and(warp::path::param().map(|hash: auth::Sha256Hash| hash.0))
.and(warp::path::end())
.and_then(auth::auth_pass).boxed()
} else {
pw_path.and_then(|_addr, _state, _hash| async move {Err(warp::reject::not_found())}).boxed()
}
};
let resp = warp::path("resp")
.and(resp_auth_key
.or(resp_auth_pass));
// /resp/<req_id>/pw/<passwd hash>
// /resp/<req_id>/si[/<num of sigs in body>]
resp
};
warp::path("auth")
.and(req.or(resp))
.recover(auth::AuthError::recover)
};
.map(source::extract(cfg.trust_x_forwarded_for))
.and_then(|req: Result<std::net::IpAddr, _>| async move { req.map_err(warp::reject::custom) });
todo!()
Ok(())
}
// /// Main entry point for web server
// pub async fn main(state: ServerState, cfg: Settings) -> eyre::Result<()>
// {
// let state = Arc::new(State::new(state, cfg.clone()));
// let state = warp::any().map(move || state.clone());
// // Extract the client IP or fail with custom rejection
// let client_ip = warp::addr::remote()
// .and(warp::header("X-Forwarded-For"))
// .map(source::extract(cfg.trust_x_forwarded_for))
// .and_then(|req: Result<std::net::IpAddr, _>| async move {req.map_err(warp::reject::custom)});
// // /auth/req - Request an auth ID and information about how to respond (data to sign / password salt / what is supported, etc)
// // /auth/resp/<req_id>/pw/<passwd hash> - Respond to an auth ID with a hashed password, salted with the salt obtained from the req call.
// // /auth/resp/<req_id>/si[/<num of sigs in body>] - Respond to an auth ID with one or more signatures of the data to be signed obtained in the auth request. If no number is provided, 1 is assumed.
// let auth = {
// let req = warp::path("req")
// .and(client_ip.clone()).and(state.clone())
// .and_then(auth::auth_req);
// let resp = {
// let resp_auth_with_state = warp::post()
// .and(client_ip.clone()).and(state.clone())
// .and(warp::path::param().map(|req_id: uuid::Uuid| req_id));
// let resp_auth_key = resp_auth_with_state.clone()
// .and(warp::path("si")
// .and(warp::path::param().map(|num: usize| std::cmp::min(std::cmp::max(1,
// num),
// cfg.max_key_sigs_per_auth_response))
// .or(warp::path::end().map(|| 1usize)).unify()))
// .and(warp::body::content_length_limit(cfg.max_body_len.0))
// .and(warp::body::bytes())
// .and_then(auth::auth_key);
// // -- Paths --
// let resp_auth_pass = {
// let pw_path = resp_auth_with_state
// .and(warp::path("pw"));
// if cfg.allow_passwd_auth {
// pw_path.and(warp::path::param().map(|hash: auth::Sha256Hash| hash.0))
// .and(warp::path::end())
// .and_then(auth::auth_pass).boxed()
// } else {
// pw_path.and_then(|_addr, _state, _hash| async move {Err(warp::reject::not_found())}).boxed()
// }
// };
// let resp = warp::path("resp")
// .and(resp_auth_key
// .or(resp_auth_pass));
// // /resp/<req_id>/pw/<passwd hash>
// // /resp/<req_id>/si[/<num of sigs in body>]
// resp
// };
// warp::path("auth")
// .and(req.or(resp))
// .recover(auth::AuthError::recover)
// };
// todo!()
// }

@ -1,292 +0,0 @@
//! Handles sessions and maintaining them
//!
//! Each active and authed client has a `Session` object associated with it. Clients can auth as multiple users within these sessions. Sessions expire after being inactive for their ttl
use super::*;
use tokio::{
time::{
self,
DelayQueue,
delay_queue,
Duration,
},
sync::{
RwLock,
RwLockReadGuard,
RwLockWriteGuard,
watch,
},
};
use std::collections::{
HashMap,
};
use std::{
task::{Context,Poll},
pin::Pin,
sync::Weak,
marker::PhantomData,
};
use server::user::UserID;
id_type!(pub SessionID: "A unique session id");
impl SessionID
{
/// Create a new random session ID.
#[inline] pub fn new() -> Self
{
Self::id_new()
}
}
#[derive(Debug)]
struct Inner
{
id: SessionID,
ttl_send: watch::Sender<Duration>,
ttl: Duration,
users: RwLock<Vec<UserID>>,
}
/// A lock to a `Session` object, temporarily preventing it's expiring TTL from destroying the underlying session object.
///
/// While this object is alive, its TTL expiring will not cause the session to be destroyed; however, it will still be removed from the `Sessions` container that created it, which will cause lookups for this `Session` to fail.
/// If the TTL expires while the object(s) of this lock are alive, the underlying session data will be destroyed once all `SessionLock`s refering to it have been dropped.
///
/// It is still possible to check if the TTL has expired using methods on the lock.
///
/// # Remarks
/// You should refrain from keeping `SessionLock`s alive longer than they need to be, as they cause the object to outlive its TTL, it could result in a potential security vulerability and/or memory leak (a leaked `SessionLock` will cause the session to valid forever, which is a huge security risk despite it being unable to be looked up).
///
/// However, if a lock is needed withing a request's context, it should be acquired as soon as possible to prevent any kind of data race that destroys the object while it's still technically find to use. This is a logical issue, not a memory safety one. It is still safe to defer the creation of locks to later on in the request's handler.
///
/// # Notes
/// This 'lock' does not cause any exclusion on waiting threads. It's not really a lock. It's (essentially) free to acquire and hold locks as long as you like, however if you keep a persistent `SessionLock` object, it may outlive its original TTL causing a potential security vulerability as well as a potential memory leak.
///
/// # Warnings
/// It is assumed that when session or session lock objects interact with a container that *it is the same container that was used to create the object*. It is the responsibility of the consumer of these APIs to ensure that a session does not access an unrelated container and the result is *logically undefined behaviour*.
#[derive(Debug)]
pub struct SessionLock<'a>(Arc<Inner>, PhantomData<&'a Session>);
impl<'a, 'b> AsRef<SessionID> for &'b SessionLock<'a>
where 'a: 'b
{
#[inline] fn as_ref(&self) -> &SessionID
{
&self.0.id
}
}
impl AsRef<SessionID> for SessionID
{
#[inline] fn as_ref(&self) -> &SessionID
{
self
}
}
//impl session (`Inner`) methods on `SessionLock`
impl<'a> SessionLock<'a>
{
pub fn id(&self) -> &SessionID
{
&self.0.id
}
pub fn ttl(&self) -> &Duration
{
&self.0.ttl
}
pub fn users(&self) -> &RwLock<Vec<UserID>>
{
&self.0.users
}
pub async fn add_user(&mut self, id: UserID)
{
self.0.users.write().await.push(id)
}
pub async fn with_users<F>(&self, mut clo: F)
where F: FnMut(&UserID)
{
for x in self.0.users.read().await.iter()
{
clo(x);
}
}
pub async fn has_user(&self, id: impl AsRef<UserID>) -> bool
{
self.0.users.read().await.contains(id.as_ref())
}
pub async fn remove_user(&mut self, id: impl AsRef<UserID>)
{
self.0.users.write().await.retain(move |x| x!= id.as_ref());
}
}
/// A `Session` object.
#[derive(Debug, Clone)]
pub struct Session(Weak<Inner>);
impl Session
{
/// Acquire a lock of this session, preventing it from being destroyed while the lock is active.
///
/// This should be used to batch transations, as it insures the subsequent sessions cannot fail mid-processing.
/// If the session is flagged for destruction while the lock is held, it will still be removed from it's `Sessions` object container, and the actual session object will be destroyed when the lock is released.
///
/// A locked session that has been destroyed is able to re-add itself to a `Sessions` container.
///
/// # Notes
/// This 'lock' does not cause any exclusion on waiting threads. It's not really a lock. It's (essentially) free to acquire and hold locks as long as you like, however if you keep a persistent `SessionLock` object, it may outlive its original TTL causing a potential security vulerability as well as a potential memory leak.
///
/// # Warnings
/// It is assumed that when session or session lock objects interact with a container that *it is the same container that was used to create the object*. It is the responsibility of the consumer of these APIs to ensure that a session does not access an unrelated container and the result is *logically undefined behaviour*.
#[inline] pub fn lock(&self) -> Option<SessionLock<'_>>
{
self.0.upgrade().map(|x| SessionLock(x, PhantomData))
}
/// Check to see if this session has not yet been destroyed.
#[inline] pub fn is_alive(&self) -> bool
{
self.0.strong_count()>0
}
/// Check to see if this session is still alive, but has been removed from its pool and is awaiting destruction.
pub async fn is_zombie(&self, cont: &Sessions) -> bool
{
OptionFuture::from(self.lock().map(|ses| async move {cont.sessions.read().await.contains_key(&ses.0.id)})).await.unwrap_or(false)
}
}
/// A container of `Session` objects.
#[derive(Debug)]
pub struct Sessions
{
sessions: Arc<RwLock<HashMap<SessionID, Arc<Inner>>>>,
}
impl Sessions
{
/// Create a new, empty, container.
#[inline] pub fn new() -> Self
{
Self {
sessions: Arc::new(RwLock::new(HashMap::new()))
}
}
/// Consume a strong session reference and start its ttl timer to remove itself from the container.
/// This spawns a new detached task that owns a `Weak` reference to the inner sessions map. If the `Sessions` container is dropped before this task completes, then nothing happens after the TTL expires.
/// The detached task does *not* prevent the `Sessions` object from being destroyed on drop.
///
/// # Locking
/// When the TTL of this session expires, and the `Sessions` container has not been dropped, then the container's write lock is acquired to remove the session from the container. The task completes immediately after, releasing the lock after the single remove operation.
///
/// # Timeing-out & cancelling
/// The client's session can refresh this expiration timer task by sending a new ttl to its `ttl_send` watch sender.
/// Usually this should be the same ttl which is set within the session's inner, but it can really be anything.
#[inline] fn detach_ttl_timer(&self, ses: Arc<Inner>, mut ttl: watch::Receiver<Duration>)
{
let intern = Arc::downgrade(&self.sessions);
let ses = Arc::downgrade(&ses);
tokio::spawn(async move {
let timed_out = if let Some(mut tm) = ttl.recv().await {
loop {
tokio::select! {
_ = time::delay_for(tm) => {
break true; // We timed out
}
nttl = ttl.recv() => {
tm = match nttl {
Some(nttl) => nttl, // Client session refreshed it's ttl
_ => return, // Client session was dropped. Return here because there's no reason to try to upgrade `ses` for removal now, it has been dropped and therefor must have been removed already.
};
}
}
}
} else {
return // Client session was dropped before we could even spawn this task. No reason to try to upgrade `ses` for removal now, it has been dropped and therefor must have been removed already.
};
if let Some(ses) = ses.upgrade() {
if timed_out {
// We timed out
info!("Session {} timed out, removing", ses.id);
} else {
// There was an error / somehow the session was dropped?
error!("Impossible error: TTL timer for session {} failed to communicate with session. Attempting removal anyway", ses.id);
}
if let Some(intern) = intern.upgrade() {
if intern.write().await.remove(&ses.id).is_some() {
trace!("Removed session {} from container, now dropping upgraded reference.", ses.id);
} else{
warn!("Failed to remove valid and alive session {} from alive container, this shouldn't happen and indicates a bug that we're working on the wrong container", ses.id);
}
} else {
// Any still-alive sessions are zombies after we free our upgraded session reference here
trace!("Failed to upgrade reference to container, it has been dropped. Exiting");
}
} else {
trace!("Session was dropped as we were about to remove it.");
}
});
}
/// Create and insert a new session with a new ID, tuned by `cfg`, and inserted into the collection. Return a session object containing this.
#[inline]
#[deprecated = "Useless without immediately calling `session.lock()` to retrieve the newly generated ID, which is a performance hit and `session.lock()` may fail causing the new session to be unindexable. Use `SessionID::new()` and `insert_new_with_id` instead."]
pub async fn insert_new(&mut self, cfg: &Settings) -> Session
{
self.insert_new_with_id(cfg, SessionID::id_new()).await
}
/// Create and insert a new `Session` object with the provided session ID using the session control options provided by `cfg`.
///
/// After inserting into the container, the new `Session` object is returned.
///
/// # Locking
/// This method acquires the container's *write* lock when inserting the session into the container. It is release immediately after the single insert operation.
///
/// It is not guaranteed that this method will complete without yielding.
/// As no concurrent calls to this function (or any others on this object) are possible due to Rust's single mutable reference rule, the only contributors to causing this call to yield are detached TTL expiration tasks which will also acquire the *write* lock when the TTL expires.
///
/// # Notes
/// It is not guaranteed that the *first* call to `session.lock()` will succeed. The only case that will cause this to happen is with improporly configured session TTLs.
/// Upon the consumer of this API retrieving this return value, if operations on the object are needed at any point the caller's context, she should immediately call `session.lock()` to prevent improporly configured TTLs destroying the session before it's enabled to be used.
///
/// If this happens, the session creation attempt should be considered to have failed, the request should return an error, and a log should be outputted informing the user that she configured the Session control TTL incorrectly; this is a configuration error.
pub async fn insert_new_with_id(&mut self, cfg: &Settings, id: SessionID) -> Session
{
let ttl = Duration::from_millis(cfg.auth_token_ttl_millis.jitter());
let (ttl_send, rx) = watch::channel(ttl);
let ses = Arc::new(Inner {
id: id.clone(),
ttl, ttl_send,
users: RwLock::new(Vec::new()),
});
let output = Session(Arc::downgrade(&ses));
self.sessions.write().await.insert(id.clone(), Arc::clone(&ses));
self.detach_ttl_timer(ses, rx);
output
}
/// Attempt to get a `Session` object of `id`.
///
/// If this method returns `None` for a contextually valid session ID, then the session is invalid to create any *new* accessors to.
/// However, if the session object has been removed due to expired TTL or another kind of invalidation, it doesn't nessisarily mean the session object has been destroyed.
/// It may still be being used by another task or request.
pub async fn get(&self, id: &SessionID) -> Option<Session>
{
self.sessions.read().await.get(id)
.map(Arc::downgrade)
.map(Session)
}
/// Remove a session with this ID from the container.
pub async fn remove(&mut self, ses: impl AsRef<SessionID>)
{
self.sessions.write().await.remove(ses.as_ref());
}
}

@ -1,46 +0,0 @@
//! Settings for web server
//!
//! Usually derived from config
use super::*;
const DEFAULT_MAX_BODY_LEN_ARESP: u64 = 1024 * 4; // 4KB
const DEFAULT_MAX_BODY_LEN: u64 = 1024 * 1024 * 4; // 4MB
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Settings
{
/// First is max body len for auth responses, 2nd is for data.
pub max_body_len: (u64, u64),
pub trust_x_forwarded_for: bool,
pub allow_passwd_auth: bool,
pub max_key_sigs_per_auth_response: usize,
/// How long is an auth request ID is valid.
///
/// A random value between these two bounds is selected
pub auth_req_ttl_millis: (u64, u64),
/// How long is an authentication token valid for an action.
///
/// A random value between these two bounds is selected
pub auth_token_ttl_millis: (u64, u64),
}
impl Default for Settings
{
#[inline]
fn default() -> Self
{
Self {
max_body_len: (DEFAULT_MAX_BODY_LEN_ARESP, DEFAULT_MAX_BODY_LEN),
trust_x_forwarded_for: false,
allow_passwd_auth: true,
max_key_sigs_per_auth_response: 16,
auth_req_ttl_millis: (4000, 6000), //4s - 6s
auth_token_ttl_millis: (1500, 2500).map(|x| x * 60), // 1.5m - 2.5m
}
}
}

@ -31,7 +31,7 @@ impl Requester
}
}
/// Rejection error when the client's IP could not be determined.
#[derive(Debug)]
pub struct NoIpError;
@ -46,7 +46,10 @@ impl fmt::Display for NoIpError
}
/// Extract the IP using the specified settings for trusting the `X-Forwarded-For` header.
pub fn extract(trust_x: bool) -> impl Fn(Option<SocketAddr>, XForwardedFor) -> Result<IpAddr,NoIpError> + Clone
///
/// # Returns
/// A filter to perform the extraction
#[inline] pub fn extract(trust_x: bool) -> impl Fn(Option<SocketAddr>, XForwardedFor) -> Result<IpAddr,NoIpError> + Clone
{
move |opt, x| {
if trust_x {

@ -1,187 +0,0 @@
//! Web server state
use super::*;
use std::{
collections::HashMap,
sync::Arc,
pin::Pin,
task::Context,
task::Poll,
fmt,error,
};
use tokio::{
sync::{
RwLock,
RwLockWriteGuard,
RwLockReadGuard,
Notify,
},
time::{
self,
DelayQueue,
delay_queue,
},
};
#[derive(Debug)]
pub struct AuthContainer
{
active_req: HashMap<Uuid, (auth::AuthRequest, delay_queue::Key)>,
timeouts: DelayQueue<Uuid>,
}
pub struct AuthPurge<'a, F = fn(auth::AuthRequest)>(&'a mut AuthContainer, F);
impl<'a, F> Future for AuthPurge<'a, F>
where F: FnMut(auth::AuthRequest) + 'a + Unpin
{
type Output = Result<(), time::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
while let Some(res) = futures::ready!(this.0.timeouts.poll_expired(cx)) {
let ent = res?;
let _ = this.0.active_req.remove(ent.get_ref()).map(|x| x.0).map(&mut this.1);
}
Poll::Ready(Ok(()))
}
}
impl AuthContainer
{
/// Gerate a new empty auth token container
fn new() -> Self
{
Self {
active_req: HashMap::new(),
timeouts: DelayQueue::new(),
}
}
/// Returns a future that purges expired entries, running the provided closure on them.
///
/// The future will yield if:
/// * The stire is not empty
/// * There are non-expired entries in the store
pub fn purge_and_then<'a, F: FnMut(auth::AuthRequest) +Unpin +'a>(&'a mut self, and_then: F) -> AuthPurge<'a, F>
{
AuthPurge(self, and_then)
}
/// Returns a future that purges expired entries. See `purge_and_then`.
#[inline] pub fn purge(&mut self) -> AuthPurge<'_>
{
AuthPurge(self, std::mem::drop)
}
/// Purge all expired entries.
#[inline] pub fn purge_now(&mut self)
{
self.purge().now_or_never();
}
/// Purge all expired entries, running the provided closure on them.
pub fn purge_now_and_then<'a, F: FnMut(auth::AuthRequest) +Unpin+'a>(&'a mut self, and_then: F)
{
self.purge_and_then(and_then).now_or_never();
}
/// Insert a request into the store, setting it to expire once its ttl is up.
pub fn insert_req(&mut self, req: auth::AuthRequest)
{
self.purge_now();
let k = self.timeouts.insert(req.id, req.ttl());
self.active_req.insert(req.id, (req, k));
}
/// Attempt to retrieve a value from the store by its ID.
///
/// # Notes
/// `AuthCacheError::Timeout` will only be returned if the request we're trying to extract has timed out *and not yet been removed* yet by an earlier, potentially unrelated, call to `handle_req` *or* `insert_req` (or an explicit purge).
/// If an error is returned you cannot rely on the accuracy of the error kind.
pub fn handle_req(&mut self, id: Uuid) -> Result<auth::AuthRequest, AuthCacheError>
{
let mut timed_out=false;
self.purge_now_and_then(|other| if other.id==id { timed_out = true; });
if timed_out {
Err(AuthCacheError::Timeout)
} else {
self.active_req.remove(&id).ok_or(AuthCacheError::Removed).map(|(v, k)| {
self.timeouts.remove(&k);
v
})
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum AuthCacheError
{
Removed,
Timeout,
}
impl error::Error for AuthCacheError{}
impl fmt::Display for AuthCacheError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self {
Self::Removed => write!(f, "id was not present"),
Self::Timeout => write!(f, "id timed out as we read it"),
}
}
}
use session::*;
#[derive(Debug)]
pub struct State
{
backend: RwLock<ServerState>,
auth_tokens: RwLock<AuthContainer>,
//TODO: user auths, public keys, hashed passwords, etc.
sessions: RwLock<Sessions>,
settings: Settings,
}
impl State
{
/// Create state from a backend state and server settings
pub fn new(backend: ServerState, settings: Settings) -> Self
{
Self {
auth_tokens: RwLock::new(AuthContainer::new()),
sessions: RwLock::new(Sessions::new()),
backend: RwLock::new(backend),
settings,
}
}
/// The session container
pub fn sessions(&self) -> &RwLock<Sessions>
{
&self.sessions
}
/// The web server settings
pub fn cfg(&self) -> &Settings
{
&self.settings
}
/// Get a write reference to the auth container
pub async fn auth_tokens(&self) -> RwLockWriteGuard<'_, AuthContainer>
{
self.auth_tokens.write().await
}
/// Get a read reference to the auth container.
///
/// Typically only useful for debugging/logging.
pub async fn auth_tokens_ref(&self) -> RwLockReadGuard<'_, AuthContainer>
{
self.auth_tokens.read().await
}
}

@ -1,20 +0,0 @@
//! Utils
/// Get a random value between these two inclusive
pub fn jitter<T>(min: T, max: T) -> T
where T: rand::distributions::uniform::SampleUniform
{
use rand::Rng;
let mut thread = rand::thread_rng();
let dist = rand::distributions::Uniform::new_inclusive(min, max);
thread.sample(dist)
}
/// Compare pointer identity
#[inline(always)] pub fn ptr_eq<T>(ptr: &T, other: &T) -> bool
{
ptr as *const T as usize ==
other as *const T as usize
}
Loading…
Cancel
Save