diff --git a/src/app.rs b/src/app.rs index a874920..f4a63d6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + /// The application name pub const NAME: &str = "unFTP"; diff --git a/src/auth.rs b/src/auth.rs index 7ad6fa4..1fbdd53 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,67 +1,6 @@ +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, - pub surname: Option, - /// 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>, // 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, -} - -impl User { - fn with_defaults(username: impl Into) -> 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 { - 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 { @@ -69,12 +8,6 @@ pub struct LookupAuthenticator { usr_detail: Option>, } -/// 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; -} - impl LookupAuthenticator { pub fn new + Send + Sync + 'static>( inner: A, @@ -119,67 +52,3 @@ impl UserDetailProvider for DefaultUserProvider { Some(User::with_defaults(username)) } } - -#[derive(Debug, Deserialize)] -pub struct JsonUserProvider { - users: Vec, -} - -#[derive(Deserialize, Clone, Debug)] -struct UserJsonObj { - username: String, - name: Option, - surname: Option, - vfs_perms: Option>, - #[allow(dead_code)] - allowed_mime_types: Option>, - root: Option, - account_enabled: Option, -} - -impl JsonUserProvider { - pub fn from_json(json: &str) -> std::result::Result { - let v: Vec = 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 { - 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), - } - }) - } -} diff --git a/src/domain.rs b/src/domain/events.rs similarity index 91% rename from src/domain.rs rename to src/domain/events.rs index fa5848f..08df7f1 100644 --- a/src/domain.rs +++ b/src/domain/events.rs @@ -1,6 +1,10 @@ +//! 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] @@ -8,10 +12,6 @@ pub trait EventDispatcher: 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 for NullEventDispatcher { async fn dispatch(&self, _event: FTPEvent) { @@ -19,6 +19,10 @@ impl EventDispatcher for NullEventDispatcher { } } +// 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 { diff --git a/src/domain/mod.rs b/src/domain/mod.rs new file mode 100644 index 0000000..5526ded --- /dev/null +++ b/src/domain/mod.rs @@ -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; diff --git a/src/domain/user.rs b/src/domain/user.rs new file mode 100644 index 0000000..44c6a1d --- /dev/null +++ b/src/domain/user.rs @@ -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, + pub surname: Option, + /// 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>, // 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, +} + +impl User { + pub fn with_defaults(username: impl Into) -> 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 { + 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; +} diff --git a/src/infra/mod.rs b/src/infra/mod.rs index d220b70..87c86d8 100644 --- a/src/infra/mod.rs +++ b/src/infra/mod.rs @@ -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; diff --git a/src/infra/pubsub.rs b/src/infra/pubsub.rs index 9f6008c..33c068f 100644 --- a/src/infra/pubsub.rs +++ b/src/infra/pubsub.rs @@ -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}; diff --git a/src/infra/usrdetail_json.rs b/src/infra/usrdetail_json.rs new file mode 100644 index 0000000..117853b --- /dev/null +++ b/src/infra/usrdetail_json.rs @@ -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, +} + +#[derive(Deserialize, Clone, Debug)] +struct UserJsonObj { + username: String, + name: Option, + surname: Option, + vfs_perms: Option>, + #[allow(dead_code)] + allowed_mime_types: Option>, + root: Option, + account_enabled: Option, +} + +impl JsonUserProvider { + pub fn from_json(json: &str) -> std::result::Result { + let v: Vec = 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 { + 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), + } + }) + } +} diff --git a/src/main.rs b/src/main.rs index 791da92..da3da85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ extern crate lazy_static; extern crate clap; -#[allow(dead_code)] mod app; mod args; mod auth; @@ -16,15 +15,14 @@ mod notify; mod storage; use crate::{ - app::libunftp_version, - args::FtpsClientAuthType, - auth::{DefaultUserProvider, JsonUserProvider}, - domain::{EventDispatcher, FTPEvent, FTPEventPayload}, - notify::FTPListener, + app::libunftp_version, args::FtpsClientAuthType, auth::DefaultUserProvider, notify::FTPListener, }; use auth::LookupAuthenticator; use clap::ArgMatches; +use domain::events::{EventDispatcher, FTPEvent, FTPEventPayload}; +use domain::user; use flate2::read::GzDecoder; +use infra::usrdetail_json::JsonUserProvider; use libunftp::{ auth as auth_spi, notification::{DataListener, PresenceListener}, @@ -52,6 +50,8 @@ use tokio::runtime::Runtime; #[cfg(feature = "pam_auth")] use unftp_auth_pam as pam; use unftp_sbe_gcs::options::AuthMethod; +use unftp_sbe_restrict::RestrictingVfs; +use unftp_sbe_rooter::RooterVfs; fn load_user_file( path: &str, @@ -91,7 +91,7 @@ fn load_user_file( fn make_auth( m: &clap::ArgMatches, -) -> Result + Send + Sync + 'static>, String> { +) -> Result + Send + Sync + 'static>, String> { let mut auth: LookupAuthenticator = match m.value_of(args::AUTH_TYPE) { None | Some("anonymous") => make_anon_auth(), Some("pam") => make_pam_auth(m), @@ -217,9 +217,9 @@ fn make_json_auth(m: &clap::ArgMatches) -> Result { } type VfsProducer = Box< - dyn (Fn() -> storage::RooterVfs< - storage::RestrictingVfs, - auth::User, + dyn (Fn() -> RooterVfs< + RestrictingVfs, + user::User, storage::SbeMeta, >) + Send + Sync, @@ -230,7 +230,7 @@ fn fs_storage_backend(log: &Logger, m: &clap::ArgMatches) -> VfsProducer { let p: PathBuf = m.value_of(args::ROOT_DIR).unwrap().into(); let sub_log = Arc::new(log.new(o!("module" => "storage"))); Box::new(move || { - storage::RooterVfs::new(storage::RestrictingVfs::new(storage::ChoosingVfs { + RooterVfs::new(RestrictingVfs::new(storage::ChoosingVfs { inner: storage::InnerVfs::File(unftp_sbe_fs::Filesystem::new(p.clone())), log: sub_log.clone(), })) @@ -295,7 +295,7 @@ fn gcs_storage_backend(log: &Logger, m: &clap::ArgMatches) -> Result "storage"))); Ok(Box::new(move || { - storage::RooterVfs::new(storage::RestrictingVfs::new(storage::ChoosingVfs { + RooterVfs::new(RestrictingVfs::new(storage::ChoosingVfs { inner: storage::InnerVfs::Cloud(unftp_sbe_gcs::CloudStorage::with_api_base( base_url.clone(), bucket.clone(), @@ -393,7 +393,7 @@ fn start_ftp_with_storage( done: tokio::sync::mpsc::Sender<()>, ) -> Result<(), String> where - S: StorageBackend + Send + Sync + 'static, + S: StorageBackend + Send + Sync + 'static, S::Metadata: Sync + Send, { let addr = String::from(arg_matches.value_of(args::BIND_ADDRESS).unwrap()); diff --git a/src/notify.rs b/src/notify.rs index 348d7ce..c9d015b 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -1,9 +1,6 @@ -use crate::{ - args, - domain::{EventDispatcher, FTPEvent, FTPEventPayload, NullEventDispatcher}, - infra::PubsubEventDispatcher, -}; +use crate::{args, infra::PubsubEventDispatcher}; +use crate::domain::events::{EventDispatcher, FTPEvent, FTPEventPayload, NullEventDispatcher}; use async_trait::async_trait; use clap::ArgMatches; use libunftp::notification::{DataEvent, EventMeta, PresenceEvent}; diff --git a/src/storage/choose.rs b/src/storage/choose.rs index d3b6b10..793ecf4 100644 --- a/src/storage/choose.rs +++ b/src/storage/choose.rs @@ -8,7 +8,7 @@ use async_trait::async_trait; use libunftp::storage; use libunftp::storage::{Fileinfo, StorageBackend}; -use crate::auth::User; +use crate::domain::user::User; /** * A virtual file system that represents either a Cloud or file system back-end. diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 0c99af8..3de1a58 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,5 +1,3 @@ mod choose; pub use choose::{ChoosingVfs, InnerVfs, SbeMeta}; -pub use unftp_sbe_restrict::RestrictingVfs; -pub use unftp_sbe_rooter::{RooterVfs, UserWithRoot};