Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,38 @@

### Upcoming release

- **BREAKING**: Refactored `Authenticator` trait to be non-generic and return `Principal` instead of a generic `User` type. This decouples authentication (verifying credentials) from user detail retrieval (obtaining full user information).
- Introduced `Principal` struct representing an authenticated user's identity (username). This is the minimal information returned by authentication.
- Introduced `UserDetailProvider` trait to convert a `Principal` into a full `UserDetail` implementation. This allows authentication and user detail lookup to be separated.
- Introduced `AuthenticationPipeline` struct that combines an `Authenticator` and a `UserDetailProvider` to provide a complete authentication flow.
- Added `DefaultUserDetailProvider` implementation that returns `DefaultUser` for convenience.
- **BREAKING**: Updated all `unftp-auth-*` crates (`unftp-auth-jsonfile`, `unftp-auth-pam`, `unftp-auth-rest`) to use the new non-generic `Authenticator` trait.
- Updated all examples and tests to use the new authentication pattern.
- [#550](https://github.com/bolcom/libunftp/pull/550) Split Authenticators from the Subject Resolving (UserDetail
Providing) concern:
- **BREAKING**: Refactored `Authenticator` trait to be non-generic and return `Principal` instead of a generic
`User`
type. This decouples authentication (verifying credentials) from user detail retrieval (obtaining full user
information).
- Introduced `Principal` struct representing an authenticated user's identity (username). This is the minimal
information returned by authentication.
- Introduced `UserDetailProvider` trait to convert a `Principal` into a full `UserDetail` implementation. This
allows
authentication and user detail lookup to be separated.
- Introduced `AuthenticationPipeline` struct that combines an `Authenticator` and a `UserDetailProvider` to provide
a
complete authentication flow.
- Added `DefaultUserDetailProvider` implementation that returns `DefaultUser` for convenience.
- **BREAKING**: Updated all `unftp-auth-*` crates (`unftp-auth-jsonfile`, `unftp-auth-pam`, `unftp-auth-rest`) to
use
the new non-generic `Authenticator` trait.
- Updated all examples and tests to use the new authentication pattern.
- [#551](https://github.com/bolcom/libunftp/pull/551) Let authenticators know the FTP Command channel TLS state

### libunftp 0.22.0

- Compile against Rust 1.92.0 in CI
- [#547](https://github.com/bolcom/libunftp/pull/547) Put metrics and proxy-protocol functionality behind features (`prometheus` and `proxy_protocol`).
- [#547](https://github.com/bolcom/libunftp/pull/547) Put metrics and proxy-protocol functionality behind features (
`prometheus` and `proxy_protocol`).
- [#548](https://github.com/bolcom/libunftp/pull/548) Fix error message typos
- [#541](https://github.com/bolcom/libunftp/pull/541) Initial MLSD (Machine List Directory) command implementation (RFC 3659)
- [#541](https://github.com/bolcom/libunftp/pull/541) Initial MLSD (Machine List Directory) command implementation (RFC
3659)
- [#541](https://github.com/bolcom/libunftp/pull/541) Fix MLST output formatting
- [#541](https://github.com/bolcom/libunftp/pull/541) Fix MLSx facts must have a terminating semicolon according to RFC 3659 section 7.2
- [#541](https://github.com/bolcom/libunftp/pull/541) Fix MLSx facts must have a terminating semicolon according to RFC
3659 section 7.2
- [#541](https://github.com/bolcom/libunftp/pull/541) Fix wrong use of metadata.uid() instead of metadata.gid()
- [#540](https://github.com/bolcom/libunftp/pull/540) Fix build with "ring" instead of "aws_lc_rs" feature
- Implement 550 error code for RNFR command
Expand Down
9 changes: 9 additions & 0 deletions crates/unftp-auth-jsonfile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,8 @@ impl Authenticator for JsonFileAuthenticator {
}

mod test {
#[allow(unused_imports)]
use libunftp::auth::ChannelEncryptionState;
#[allow(unused_imports)]
use libunftp::auth::ClientCert;

Expand Down Expand Up @@ -530,6 +532,7 @@ mod test {
certificate_chain: None,
password: Some("".into()),
source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
command_channel_security: ChannelEncryptionState::Plaintext,
},
)
.await
Expand All @@ -545,6 +548,7 @@ mod test {
certificate_chain: None,
password: Some("".into()),
source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(128, 0, 0, 1)),
command_channel_security: ChannelEncryptionState::Plaintext,
},
)
.await
Expand Down Expand Up @@ -690,6 +694,7 @@ mod test {
certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
password: Some("has a password".into()),
source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
command_channel_security: ChannelEncryptionState::Plaintext,
},
)
.await
Expand All @@ -706,6 +711,7 @@ mod test {
certificate_chain: None,
password: Some("has a password".into()),
source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
command_channel_security: ChannelEncryptionState::Plaintext,
},
)
.await
Expand All @@ -723,6 +729,7 @@ mod test {
certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
password: None,
source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
command_channel_security: ChannelEncryptionState::Plaintext,
},
)
.await
Expand All @@ -739,6 +746,7 @@ mod test {
certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
password: None,
source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
command_channel_security: ChannelEncryptionState::Plaintext,
},
)
.await
Expand All @@ -756,6 +764,7 @@ mod test {
certificate_chain: Some(vec![ClientCert(client_cert.clone())]),
password: None,
source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
command_channel_security: ChannelEncryptionState::Plaintext,
},
)
.await
Expand Down
2 changes: 1 addition & 1 deletion src/auth/anonymous.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct AnonymousAuthenticator;
impl Authenticator for AnonymousAuthenticator {
#[allow(clippy::type_complexity)]
#[tracing_attributes::instrument]
async fn authenticate(&self, username: &str, _password: &Credentials) -> Result<Principal, AuthenticationError> {
async fn authenticate(&self, username: &str, _creds: &Credentials) -> Result<Principal, AuthenticationError> {
Ok(Principal {
username: username.to_string(),
})
Expand Down
12 changes: 12 additions & 0 deletions src/auth/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ pub struct Credentials {
pub certificate_chain: Option<Vec<ClientCert>>,
/// The IP address of the user's connection
pub source_ip: std::net::IpAddr,
/// Indicates the security state of the FTP server command channel
pub command_channel_security: ChannelEncryptionState,
}

impl From<&str> for Credentials {
Expand All @@ -123,6 +125,7 @@ impl From<&str> for Credentials {
password: Some(String::from(s)),
certificate_chain: None,
source_ip: [127, 0, 0, 1].into(),
command_channel_security: ChannelEncryptionState::Plaintext,
}
}
}
Expand Down Expand Up @@ -157,3 +160,12 @@ impl AsRef<[u8]> for ClientCert {
&self.0
}
}

/// Represents the encryption state of a channel (command or data).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelEncryptionState {
/// The channel is using plaintext (unencrypted)
Plaintext,
/// The channel is using TLS encryption
Tls,
}
2 changes: 1 addition & 1 deletion src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub use anonymous::AnonymousAuthenticator;

pub(crate) mod authenticator;
#[allow(unused_imports)]
pub use authenticator::{AuthenticationError, Authenticator, ClientCert, Credentials, Principal};
pub use authenticator::{AuthenticationError, Authenticator, ChannelEncryptionState, ClientCert, Credentials, Principal};

mod user;
pub use user::{DefaultUser, DefaultUserDetailProvider, UserDetail, UserDetailError, UserDetailProvider};
Expand Down
15 changes: 9 additions & 6 deletions src/server/controlchan/commands/pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,24 @@
// therefore the responsibility of the user-FTP process to hide
// the sensitive password information.

use crate::server::failed_logins::LockState;
use crate::{
auth::UserDetail,
auth::{ChannelEncryptionState, UserDetail},
server::{
chancomms::ControlChanMsg,
controlchan::{
Reply, ReplyCode,
error::ControlChanError,
handler::{CommandContext, CommandHandler},
},
failed_logins::LockState,
password,
session::SessionState,
},
storage::{Metadata, StorageBackend},
};
use async_trait::async_trait;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::Sender;
use tokio::time::sleep;
use std::{sync::Arc, time::Duration};
use tokio::{sync::mpsc::Sender, time::sleep};

#[derive(Debug)]
pub struct Pass {
Expand Down Expand Up @@ -75,6 +73,11 @@ where
password: Some(pass),
source_ip: session.source.ip(),
certificate_chain: session.cert_chain.clone(),
command_channel_security: if session.cmd_tls {
ChannelEncryptionState::Tls
} else {
ChannelEncryptionState::Plaintext
},
};
let failed_logins = session.failed_logins.clone();
let source_ip = session.source.ip();
Expand Down
10 changes: 7 additions & 3 deletions src/server/controlchan/commands/user.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::auth::{AuthenticationError, Credentials};
use crate::{
auth::UserDetail,
auth::{AuthenticationError, ChannelEncryptionState, Credentials, UserDetail},
server::{
controlchan::{
Reply, ReplyCode,
Expand Down Expand Up @@ -48,6 +47,11 @@ where
certificate_chain: session.cert_chain.clone(),
password: None,
source_ip: session.source.ip(),
command_channel_security: if session.cmd_tls {
ChannelEncryptionState::Tls
} else {
ChannelEncryptionState::Plaintext
},
},
)
.await;
Expand Down Expand Up @@ -118,7 +122,7 @@ mod tests {
#[async_trait]
#[allow(unused)]
impl Authenticator for Auth {
async fn authenticate(&self, username: &str, creds: &Credentials) -> std::result::Result<Principal, AuthenticationError> {
async fn authenticate(&self, username: &str, _creds: &Credentials) -> std::result::Result<Principal, AuthenticationError> {
if self.auth_ok {
Ok(Principal {
username: username.to_string(),
Expand Down
Loading