use std::{fmt, net::SocketAddr, sync::Arc};
use bytes::Bytes;
use super::{SaslMechanism, WireError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaslClientSession {
node_id: i32,
host: String,
port: u16,
addr: SocketAddr,
}
impl SaslClientSession {
#[must_use]
pub const fn new(node_id: i32, host: String, port: u16, addr: SocketAddr) -> Self {
Self {
node_id,
host,
port,
addr,
}
}
#[must_use]
pub const fn node_id(&self) -> i32 {
self.node_id
}
#[must_use]
pub fn host(&self) -> &str {
&self.host
}
#[must_use]
pub const fn port(&self) -> u16 {
self.port
}
#[must_use]
pub const fn addr(&self) -> SocketAddr {
self.addr
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SaslClientAction {
Send(Bytes),
Complete,
}
pub trait SaslClientAuthenticator: fmt::Debug + Send + Sync + 'static {
fn mechanism(&self) -> SaslMechanism;
fn start(&self) -> Result<SaslClientAction, WireError>;
fn next(&self, challenge: &[u8]) -> Result<SaslClientAction, WireError>;
}
pub trait SaslClientAuthenticatorFactory: fmt::Debug + Send + Sync + 'static {
fn mechanism(&self) -> SaslMechanism;
fn create(
&self,
session: &SaslClientSession,
) -> Result<SaslClientAuthenticatorHandle, WireError>;
}
#[derive(Clone)]
pub struct SaslClientAuthenticatorHandle {
inner: Arc<dyn SaslClientAuthenticator>,
}
impl SaslClientAuthenticatorHandle {
pub fn new(authenticator: impl SaslClientAuthenticator) -> Self {
Self {
inner: Arc::new(authenticator),
}
}
pub fn mechanism(&self) -> SaslMechanism {
self.inner.mechanism()
}
pub(crate) fn start(&self) -> Result<SaslClientAction, WireError> {
self.inner.start()
}
pub(crate) fn next(&self, challenge: &[u8]) -> Result<SaslClientAction, WireError> {
self.inner.next(challenge)
}
}
impl fmt::Debug for SaslClientAuthenticatorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SaslClientAuthenticatorHandle")
.field("mechanism", &self.mechanism())
.finish_non_exhaustive()
}
}
impl PartialEq for SaslClientAuthenticatorHandle {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
#[derive(Clone)]
pub struct SaslClientAuthenticatorFactoryHandle {
inner: Arc<dyn SaslClientAuthenticatorFactory>,
}
impl SaslClientAuthenticatorFactoryHandle {
pub fn new(factory: impl SaslClientAuthenticatorFactory) -> Self {
Self {
inner: Arc::new(factory),
}
}
pub fn mechanism(&self) -> SaslMechanism {
self.inner.mechanism()
}
pub(crate) fn create(
&self,
session: &SaslClientSession,
) -> Result<SaslClientAuthenticatorHandle, WireError> {
self.inner.create(session)
}
}
impl fmt::Debug for SaslClientAuthenticatorFactoryHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SaslClientAuthenticatorFactoryHandle")
.field("mechanism", &self.mechanism())
.finish_non_exhaustive()
}
}
impl PartialEq for SaslClientAuthenticatorFactoryHandle {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::missing_assert_message,
reason = "Unit tests keep assertions compact around simple API shape."
)]
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};
use super::{
SaslClientAction, SaslClientAuthenticator, SaslClientAuthenticatorFactory,
SaslClientAuthenticatorFactoryHandle, SaslClientAuthenticatorHandle, SaslClientSession,
};
use crate::wire::{SaslMechanism, WireError};
#[derive(Debug)]
struct FactoryAuthenticator {
id: usize,
host: String,
}
impl SaslClientAuthenticator for FactoryAuthenticator {
fn mechanism(&self) -> SaslMechanism {
SaslMechanism::Plain
}
fn start(&self) -> Result<SaslClientAction, WireError> {
Ok(SaslClientAction::Send(
format!("{}:{}", self.id, self.host).into(),
))
}
fn next(&self, _challenge: &[u8]) -> Result<SaslClientAction, WireError> {
Ok(SaslClientAction::Complete)
}
}
#[derive(Debug)]
struct CountingFactory {
next_id: Arc<AtomicUsize>,
}
impl SaslClientAuthenticatorFactory for CountingFactory {
fn mechanism(&self) -> SaslMechanism {
SaslMechanism::Plain
}
fn create(
&self,
session: &SaslClientSession,
) -> Result<SaslClientAuthenticatorHandle, WireError> {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
Ok(SaslClientAuthenticatorHandle::new(FactoryAuthenticator {
id,
host: session.host().to_owned(),
}))
}
}
#[test]
fn sasl_factory_creates_distinct_authenticators_per_session() {
let factory = SaslClientAuthenticatorFactoryHandle::new(CountingFactory {
next_id: Arc::new(AtomicUsize::new(1)),
});
let session = SaslClientSession::new(
7,
"broker.example.com".to_owned(),
9092,
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9092),
);
let first = factory.create(&session).unwrap();
let second = factory.create(&session).unwrap();
assert_eq!(factory.mechanism(), SaslMechanism::Plain);
assert_eq!(
first.start().unwrap(),
SaslClientAction::Send("1:broker.example.com".into())
);
assert_eq!(
second.start().unwrap(),
SaslClientAction::Send("2:broker.example.com".into())
);
}
#[cfg(feature = "gssapi")]
#[test]
fn gssapi_authenticator_uses_hostbased_kafka_service_name() {
use crate::wire::SaslClientAuthenticator as _;
let authenticator = crate::wire::GssapiAuthenticator::new(
"kafka".to_owned(),
"broker.example.com".to_owned(),
);
assert_eq!(authenticator.mechanism(), SaslMechanism::Gssapi);
assert_eq!(authenticator.target_name(), "kafka@broker.example.com");
}
}