use std::any::Any;
use std::collections::HashSet;
use std::sync::Arc;
use rocketmq_error::RocketMQError;
use rocketmq_error::RocketMQResult;
use crate::authentication::provider::AuthenticationProvider;
use crate::authorization::context::authentication_context::AuthenticationContext;
use crate::config::AuthConfig;
#[allow(async_fn_in_trait)]
pub trait AbstractAuthenticationStrategy: Send + Sync {
fn auth_config(&self) -> &AuthConfig;
fn authentication_white_set(&self) -> &HashSet<String>;
fn authentication_provider(&self) -> Option<&dyn Any>;
async fn do_evaluate<C: AuthenticationContext>(&self, context: &C) -> RocketMQResult<()> {
if !self.auth_config().authentication_enabled {
return Ok(());
}
let provider = match self.authentication_provider() {
Some(p) => p,
None => return Ok(()),
};
self.authenticate_with_provider(provider, context).await
}
async fn authenticate_with_provider<C: AuthenticationContext>(
&self,
_provider: &dyn Any,
_context: &C,
) -> RocketMQResult<()>;
fn is_whitelisted(&self, rpc_code: &str) -> bool {
self.authentication_white_set().contains(rpc_code)
}
}
pub struct BaseAuthenticationStrategy<P>
where
P: AuthenticationProvider,
{
auth_config: AuthConfig,
authentication_white_set: HashSet<String>,
authentication_provider: Option<Arc<P>>,
}
impl<P> BaseAuthenticationStrategy<P>
where
P: AuthenticationProvider + Send + Sync + 'static,
{
pub fn new(auth_config: AuthConfig, provider: Option<Arc<P>>) -> Self {
let mut authentication_white_set = HashSet::new();
let whitelist = auth_config.authentication_whitelist.to_string();
if !whitelist.is_empty() {
for rpc_code in whitelist.split(',') {
let trimmed = rpc_code.trim();
if !trimmed.is_empty() {
authentication_white_set.insert(trimmed.to_string());
}
}
}
Self {
auth_config,
authentication_white_set,
authentication_provider: provider,
}
}
pub async fn initialize_provider(
&mut self,
_metadata_service: Option<Arc<dyn Any + Send + Sync>>,
) -> RocketMQResult<()> {
Ok(())
}
pub fn provider(&self) -> Option<&P> {
self.authentication_provider.as_deref()
}
}
#[allow(async_fn_in_trait)]
impl<P> AbstractAuthenticationStrategy for BaseAuthenticationStrategy<P>
where
P: AuthenticationProvider + Send + Sync + 'static,
{
fn auth_config(&self) -> &AuthConfig {
&self.auth_config
}
fn authentication_white_set(&self) -> &HashSet<String> {
&self.authentication_white_set
}
fn authentication_provider(&self) -> Option<&dyn Any> {
self.authentication_provider.as_ref().map(|p| p.as_ref() as &dyn Any)
}
async fn authenticate_with_provider<C: AuthenticationContext>(
&self,
_provider: &dyn Any,
_context: &C,
) -> RocketMQResult<()> {
if self.authentication_provider.is_some() {
return Err(RocketMQError::authentication_failed(
"Provider authentication not yet implemented for generic context",
));
}
Err(RocketMQError::authentication_failed(
"No authentication provider available",
))
}
}
pub trait AuthenticationStrategyFactory {
fn create_strategy(
auth_config: AuthConfig,
metadata_service: Option<Arc<dyn Any + Send + Sync>>,
) -> RocketMQResult<Self>
where
Self: Sized;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::authentication::context::default_authentication_context::DefaultAuthenticationContext;
use crate::authentication::provider::DefaultAuthenticationProvider;
#[tokio::test]
async fn test_base_strategy_creation() {
let config = AuthConfig::default();
let provider = Arc::new(DefaultAuthenticationProvider::new());
let strategy = BaseAuthenticationStrategy::new(config, Some(provider));
assert!(strategy.provider().is_some());
assert!(strategy.authentication_white_set().is_empty());
}
#[tokio::test]
async fn test_whitelist_parsing() {
let config = AuthConfig {
authentication_whitelist: "100,200, 300 ".into(),
..Default::default()
};
let strategy: BaseAuthenticationStrategy<DefaultAuthenticationProvider> =
BaseAuthenticationStrategy::new(config, None);
assert_eq!(strategy.authentication_white_set().len(), 3);
assert!(strategy.authentication_white_set().contains("100"));
assert!(strategy.authentication_white_set().contains("200"));
assert!(strategy.authentication_white_set().contains("300"));
}
#[tokio::test]
async fn test_authentication_disabled() {
let config = AuthConfig {
authentication_enabled: false,
..Default::default()
};
let strategy: BaseAuthenticationStrategy<DefaultAuthenticationProvider> =
BaseAuthenticationStrategy::new(config, None);
let context = DefaultAuthenticationContext::new();
let result = strategy.do_evaluate(&context).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_whitelisted_rpc_code() {
let config = AuthConfig {
authentication_enabled: true,
authentication_whitelist: "SEND_MESSAGE,PULL_MESSAGE".into(),
..Default::default()
};
let strategy: BaseAuthenticationStrategy<DefaultAuthenticationProvider> =
BaseAuthenticationStrategy::new(config, None);
assert!(strategy.authentication_white_set().contains("SEND_MESSAGE"));
}
}