feat: Start dummy in-memory database
This commit is contained in:
parent
1ef02c58dd
commit
bdb5008914
9 changed files with 181 additions and 3 deletions
36
src/db/common.rs
Normal file
36
src/db/common.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use tokio::sync::RwLock;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::db::{DatabaseInterface, UserRef};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Database<D: DatabaseInterface> {
|
||||
pub inner: Arc<RwLock<D>>,
|
||||
}
|
||||
|
||||
impl<D: DatabaseInterface> Database<D> {
|
||||
pub fn new(db: D) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(db)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DatabaseInterface> Database<D> {
|
||||
/// Return false if the user doesn't exist, or the password is wrong.
|
||||
///
|
||||
/// TODO: should we return something else when the account doesn't exist?
|
||||
/// or is it a feature to behave in the same way?
|
||||
///
|
||||
/// TODO: we may want to make sure the method runs in constant time
|
||||
/// to avoid leaking information about existing users...
|
||||
/// or maybe we do not care.
|
||||
async fn check_password(&self, user: &UserRef, password: &str) -> bool {
|
||||
let Some(user) = self.get_user(user).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
user.password == password
|
||||
}
|
||||
}
|
||||
14
src/db/error.rs
Normal file
14
src/db/error.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::db::UserRef;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserAlreadyExists(pub UserRef);
|
||||
|
||||
impl fmt::Display for UserAlreadyExists {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "User already exists: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UserAlreadyExists {}
|
||||
5
src/db/group.rs
Normal file
5
src/db/group.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Group {
|
||||
name: String,
|
||||
domain: String,
|
||||
}
|
||||
17
src/db/interface.rs
Normal file
17
src/db/interface.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use crate::db::error::*;
|
||||
use crate::db::{Database, User, UserRef};
|
||||
|
||||
impl<D: DatabaseInterface> DatabaseInterface for Database<D> {
|
||||
async fn get_user(&self, user: &UserRef) -> Option<User> {
|
||||
self.inner.read().await.get_user(user).await
|
||||
}
|
||||
|
||||
async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists> {
|
||||
self.inner.write().await.create_user(user).await
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DatabaseInterface {
|
||||
async fn get_user(&self, user: &UserRef) -> Option<User>;
|
||||
async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists>;
|
||||
}
|
||||
38
src/db/memory.rs
Normal file
38
src/db/memory.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use std::future::{Future, ready};
|
||||
|
||||
use crate::db::error::*;
|
||||
use crate::db::{Database, DatabaseInterface, Group, User, UserRef};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MemoryDatabase {
|
||||
pub users: Vec<User>,
|
||||
pub groups: Vec<Group>,
|
||||
}
|
||||
|
||||
impl MemoryDatabase {
|
||||
pub fn new() -> Database<Self> {
|
||||
Database::new(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseInterface for MemoryDatabase {
|
||||
fn get_user(&self, req_user: &UserRef) -> impl Future<Output = Option<User>> {
|
||||
for user in &self.users {
|
||||
if user.username == req_user.username && user.domain == req_user.domain {
|
||||
return ready(Some(user.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
ready(None)
|
||||
}
|
||||
|
||||
async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists> {
|
||||
let user_ref = user.user_ref();
|
||||
if self.get_user(&user_ref).await.is_some() {
|
||||
return Err(UserAlreadyExists(user_ref));
|
||||
}
|
||||
|
||||
self.users.push(user);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
11
src/db/mod.rs
Normal file
11
src/db/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
mod common;
|
||||
pub use common::Database;
|
||||
pub mod error;
|
||||
mod group;
|
||||
pub use group::Group;
|
||||
mod interface;
|
||||
pub use interface::DatabaseInterface;
|
||||
mod memory;
|
||||
pub use memory::MemoryDatabase;
|
||||
mod user;
|
||||
pub use user::{User, UserRef};
|
||||
37
src/db/user.rs
Normal file
37
src/db/user.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserRef {
|
||||
pub username: String,
|
||||
pub domain: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for UserRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}@{}", self.username, self.domain)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct User {
|
||||
pub username: String,
|
||||
pub domain: String,
|
||||
pub password: String,
|
||||
pub mail: String,
|
||||
// recovery_mail: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn user_ref(&self) -> UserRef {
|
||||
UserRef {
|
||||
username: self.username.clone(),
|
||||
domain: self.domain.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for User {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}@{}", self.username, self.domain)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use ldap3_proto::LdapMsg;
|
||||
|
||||
use crate::db::{Database, DatabaseInterface};
|
||||
use crate::ldap::{LdapStream, LdapStreamError};
|
||||
|
||||
#[tracing::instrument(name = "ldap", skip(s), fields(session = %s.session))]
|
||||
pub async fn ldap_handler(mut s: LdapStream) {
|
||||
#[tracing::instrument(name = "ldap", skip(s, db), fields(session = %s.session))]
|
||||
pub async fn ldap_handler<D: DatabaseInterface>(mut s: LdapStream, db: Database<D>) {
|
||||
tracing::info! {
|
||||
remote_addr = ?s.remote_addr,
|
||||
"New client connection"
|
||||
|
|
|
|||
21
src/main.rs
21
src/main.rs
|
|
@ -1,16 +1,31 @@
|
|||
// #![deny(warnings)]
|
||||
|
||||
mod cli;
|
||||
mod db;
|
||||
mod error;
|
||||
mod ldap;
|
||||
mod listener;
|
||||
mod stream;
|
||||
|
||||
use cli::CliArgs;
|
||||
use db::{Database, DatabaseInterface, MemoryDatabase, User};
|
||||
use error::GlobalError;
|
||||
use ldap::ldap_handler;
|
||||
use listener::ListenerPath;
|
||||
|
||||
async fn create_dummy_users<D: DatabaseInterface>(db: &mut Database<D>) {
|
||||
for domain in &["a", "b", "c"] {
|
||||
db.create_user(User {
|
||||
username: domain.to_string(),
|
||||
domain: format!("{domain}.localhost"),
|
||||
password: "adminadmin".to_string(),
|
||||
mail: format!("{domain}@{domain}.localhost"),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), GlobalError> {
|
||||
let cli: CliArgs = argh::from_env();
|
||||
|
|
@ -25,10 +40,14 @@ async fn main() -> Result<(), GlobalError> {
|
|||
|
||||
let listener = ListenerPath::new(&cli.listen)?.listener().await?;
|
||||
|
||||
let mut db = MemoryDatabase::new();
|
||||
create_dummy_users(&mut db).await;
|
||||
|
||||
// If the connection is None, it's because the client aborted early
|
||||
// so there's nothing to do about it.
|
||||
while let Some(stream) = listener.accept_ldap().await? {
|
||||
tokio::spawn(ldap_handler(stream));
|
||||
let db = db.clone();
|
||||
tokio::spawn(ldap_handler(stream, db));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Reference in a new issue