Skip to content

Commit

Permalink
Move more things to domain and infra packages
Browse files Browse the repository at this point in the history
This is in preparation for adding a REST or HTTP user detail provider.
I'm moving code into the `domain` and `infra` modules. The `domain`
module contains the traits to be implemented in the infra packages.
  • Loading branch information
hannesdejager committed Feb 26, 2024
1 parent a79f96c commit 16a0b65
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 160 deletions.
2 changes: 2 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(dead_code)]

/// The application name
pub const NAME: &str = "unFTP";

Expand Down
135 changes: 2 additions & 133 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,80 +1,13 @@
use crate::domain::user::{User, UserDetailProvider};
use async_trait::async_trait;
use libunftp::auth::{AuthenticationError, Credentials, DefaultUser, UserDetail};
use serde::Deserialize;
use std::fmt::Formatter;
use std::path::PathBuf;
use unftp_sbe_restrict::{UserWithPermissions, VfsOperations};

/// The unFTP user details
#[derive(Debug, PartialEq, Eq)]
pub struct User {
pub username: String,
pub name: Option<String>,
pub surname: Option<String>,
/// Tells whether this user can log in or not.
pub account_enabled: bool,
/// What FTP commands can the user perform
pub vfs_permissions: VfsOperations,
/// For some users we know they will only upload a certain type of file
pub allowed_mime_types: Option<Vec<String>>, // TODO: Look at https://crates.io/crates/infer to do this
/// The user's home directory relative to the storage back-end root
pub root: Option<PathBuf>,
}

impl User {
fn with_defaults(username: impl Into<String>) -> Self {
User {
username: username.into(),
name: None,
surname: None,
account_enabled: true,
vfs_permissions: VfsOperations::all(),
allowed_mime_types: None,
root: None,
}
}
}

impl UserDetail for User {
fn account_enabled(&self) -> bool {
self.account_enabled
}
}

impl std::fmt::Display for User {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"User(username: {:?}, name: {:?}, surname: {:?})",
self.username, self.name, self.surname
)
}
}

impl crate::storage::UserWithRoot for User {
fn user_root(&self) -> Option<PathBuf> {
self.root.clone()
}
}

impl UserWithPermissions for User {
fn permissions(&self) -> VfsOperations {
self.vfs_permissions
}
}
use libunftp::auth::{AuthenticationError, Credentials, DefaultUser};

#[derive(Debug)]
pub struct LookupAuthenticator {
inner: Box<dyn libunftp::auth::Authenticator<DefaultUser>>,
usr_detail: Option<Box<dyn UserDetailProvider + Send + Sync>>,
}

/// Implementation of UserDetailProvider can look up and provide FTP user account details from
/// a source.
pub trait UserDetailProvider: std::fmt::Debug {
fn provide_user_detail(&self, username: &str) -> Option<User>;
}

impl LookupAuthenticator {
pub fn new<A: libunftp::auth::Authenticator<DefaultUser> + Send + Sync + 'static>(
inner: A,
Expand Down Expand Up @@ -119,67 +52,3 @@ impl UserDetailProvider for DefaultUserProvider {
Some(User::with_defaults(username))
}
}

#[derive(Debug, Deserialize)]
pub struct JsonUserProvider {
users: Vec<UserJsonObj>,
}

#[derive(Deserialize, Clone, Debug)]
struct UserJsonObj {
username: String,
name: Option<String>,
surname: Option<String>,
vfs_perms: Option<Vec<String>>,
#[allow(dead_code)]
allowed_mime_types: Option<Vec<String>>,
root: Option<String>,
account_enabled: Option<bool>,
}

impl JsonUserProvider {
pub fn from_json(json: &str) -> std::result::Result<JsonUserProvider, String> {
let v: Vec<UserJsonObj> = serde_json::from_str(json).map_err(|e| format!("{:?}", e))?;
Ok(JsonUserProvider { users: v })
}
}

impl UserDetailProvider for JsonUserProvider {
fn provide_user_detail(&self, username: &str) -> Option<User> {
self.users.iter().find(|u| u.username == username).map(|u| {
let u = u.clone();
User {
username: u.username,
name: u.name,
surname: u.surname,
account_enabled: u.account_enabled.unwrap_or(true),
vfs_permissions: u.vfs_perms.map_or(VfsOperations::all(), |p| {
p.iter()
.fold(VfsOperations::all(), |ops, s| match s.as_str() {
"none" => VfsOperations::empty(),
"all" => VfsOperations::all(),
"-mkdir" => ops - VfsOperations::MK_DIR,
"-rmdir" => ops - VfsOperations::RM_DIR,
"-del" => ops - VfsOperations::DEL,
"-ren" => ops - VfsOperations::RENAME,
"-md5" => ops - VfsOperations::MD5,
"-get" => ops - VfsOperations::GET,
"-put" => ops - VfsOperations::PUT,
"-list" => ops - VfsOperations::LIST,
"+mkdir" => ops | VfsOperations::MK_DIR,
"+rmdir" => ops | VfsOperations::RM_DIR,
"+del" => ops | VfsOperations::DEL,
"+ren" => ops | VfsOperations::RENAME,
"+md5" => ops | VfsOperations::MD5,
"+get" => ops | VfsOperations::GET,
"+put" => ops | VfsOperations::PUT,
"+list" => ops | VfsOperations::LIST,
_ => ops,
})
}),
allowed_mime_types: None,
root: u.root.map(PathBuf::from),
}
})
}
}
14 changes: 9 additions & 5 deletions src/domain.rs → src/domain/events.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
//! Event definitions
//!
//! Packages elsewhere e.g. in the [`infra`](crate::infra) module implements these traits defined here.
use async_trait::async_trait;
use serde::__private::fmt::Debug;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;

// EventDispatcher can send events to the outside world.
#[async_trait]
pub trait EventDispatcher<T>: Send + Sync + Debug {
async fn dispatch(&self, event: T);
}

// An EventDispatcher that dispatches to the void of nothingness.
#[derive(Debug)]
pub struct NullEventDispatcher {}

#[async_trait]
impl EventDispatcher<FTPEvent> for NullEventDispatcher {
async fn dispatch(&self, _event: FTPEvent) {
// Do Nothing
}
}

// An EventDispatcher that dispatches to the void of nothingness.
#[derive(Debug)]
pub struct NullEventDispatcher {}

// The event that will be sent
#[derive(Serialize, Deserialize, Debug)]
pub struct FTPEvent {
Expand Down
4 changes: 4 additions & 0 deletions src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! The domain module contains code to be dependent upon elsewhere in this project. It
//! should itself not depend on any modules in this project.
pub mod events;
pub mod user;
69 changes: 69 additions & 0 deletions src/domain/user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use libunftp::auth::UserDetail;
use std::fmt::{Debug, Display, Formatter};
use std::path::PathBuf;
use unftp_sbe_restrict::{UserWithPermissions, VfsOperations};
use unftp_sbe_rooter::UserWithRoot;

/// The unFTP user details
#[derive(Debug, PartialEq, Eq)]
pub struct User {
pub username: String,
pub name: Option<String>,
pub surname: Option<String>,
/// Tells whether this user can log in or not.
pub account_enabled: bool,
/// What FTP commands can the user perform
pub vfs_permissions: VfsOperations,
/// For some users we know they will only upload a certain type of file
pub allowed_mime_types: Option<Vec<String>>, // TODO: Look at https://crates.io/crates/infer to do this
/// The user's home directory relative to the storage back-end root
pub root: Option<PathBuf>,
}

impl User {
pub fn with_defaults(username: impl Into<String>) -> Self {
User {
username: username.into(),
name: None,
surname: None,
account_enabled: true,
vfs_permissions: VfsOperations::all(),
allowed_mime_types: None,
root: None,
}
}
}

impl UserDetail for User {
fn account_enabled(&self) -> bool {
self.account_enabled
}
}

impl Display for User {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"User(username: {:?}, name: {:?}, surname: {:?})",
self.username, self.name, self.surname
)
}
}

impl UserWithRoot for User {
fn user_root(&self) -> Option<PathBuf> {
self.root.clone()
}
}

impl UserWithPermissions for User {
fn permissions(&self) -> VfsOperations {
self.vfs_permissions
}
}

/// Implementation of UserDetailProvider can look up and provide FTP user account details from
/// a source.
pub trait UserDetailProvider: Debug {
fn provide_user_detail(&self, username: &str) -> Option<User>;
}
4 changes: 4 additions & 0 deletions src/infra/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
//! Infra contains infrastructure specific implementations of things in the [`domain`](crate::domain)
//! module.
mod pubsub;
mod workload_identity;

pub mod usrdetail_json;

pub use pubsub::PubsubEventDispatcher;
2 changes: 1 addition & 1 deletion src/infra/pubsub.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::domain::{EventDispatcher, FTPEvent, FTPEventPayload};
use crate::domain::events::{EventDispatcher, FTPEvent, FTPEventPayload};
use crate::infra::workload_identity;
use async_trait::async_trait;
use http::{header, Method, Request, StatusCode, Uri};
Expand Down
69 changes: 69 additions & 0 deletions src/infra/usrdetail_json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::domain::user::{User, UserDetailProvider};
use serde::Deserialize;
use std::path::PathBuf;
use unftp_sbe_restrict::VfsOperations;

/// A [`UserDetail`] provider that gets user details from a JSON file.
#[derive(Debug, Deserialize)]
pub struct JsonUserProvider {
users: Vec<UserJsonObj>,
}

#[derive(Deserialize, Clone, Debug)]
struct UserJsonObj {
username: String,
name: Option<String>,
surname: Option<String>,
vfs_perms: Option<Vec<String>>,
#[allow(dead_code)]
allowed_mime_types: Option<Vec<String>>,
root: Option<String>,
account_enabled: Option<bool>,
}

impl JsonUserProvider {
pub fn from_json(json: &str) -> std::result::Result<JsonUserProvider, String> {
let v: Vec<UserJsonObj> = serde_json::from_str(json).map_err(|e| format!("{:?}", e))?;
Ok(JsonUserProvider { users: v })
}
}

impl UserDetailProvider for JsonUserProvider {
fn provide_user_detail(&self, username: &str) -> Option<User> {
self.users.iter().find(|u| u.username == username).map(|u| {
let u = u.clone();
User {
username: u.username,
name: u.name,
surname: u.surname,
account_enabled: u.account_enabled.unwrap_or(true),
vfs_permissions: u.vfs_perms.map_or(VfsOperations::all(), |p| {
p.iter()
.fold(VfsOperations::all(), |ops, s| match s.as_str() {
"none" => VfsOperations::empty(),
"all" => VfsOperations::all(),
"-mkdir" => ops - VfsOperations::MK_DIR,
"-rmdir" => ops - VfsOperations::RM_DIR,
"-del" => ops - VfsOperations::DEL,
"-ren" => ops - VfsOperations::RENAME,
"-md5" => ops - VfsOperations::MD5,
"-get" => ops - VfsOperations::GET,
"-put" => ops - VfsOperations::PUT,
"-list" => ops - VfsOperations::LIST,
"+mkdir" => ops | VfsOperations::MK_DIR,
"+rmdir" => ops | VfsOperations::RM_DIR,
"+del" => ops | VfsOperations::DEL,
"+ren" => ops | VfsOperations::RENAME,
"+md5" => ops | VfsOperations::MD5,
"+get" => ops | VfsOperations::GET,
"+put" => ops | VfsOperations::PUT,
"+list" => ops | VfsOperations::LIST,
_ => ops,
})
}),
allowed_mime_types: None,
root: u.root.map(PathBuf::from),
}
})
}
}
Loading

0 comments on commit 16a0b65

Please sign in to comment.