rocketmq_auth/authentication/strategy/authentication_strategy.rs
1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # Authentication Strategy
16//!
17//! This module provides the core `AuthenticationStrategy` trait for implementing
18//! pluggable authentication strategies in RocketMQ.
19//!
20//! ## Overview
21//!
22//! The `AuthenticationStrategy` trait defines a unified interface for authentication
23//! mechanisms, supporting multiple authentication strategies such as:
24//! - AK/SK (Access Key / Secret Key) authentication
25//! - Token-based authentication
26//! - mTLS (mutual TLS) authentication
27//! - Custom authentication strategies
28//!
29//! ## Design
30//!
31//! The trait is designed to be:
32//! - **Object-safe**: Can be used with `Arc<dyn AuthenticationStrategy>` for dynamic dispatch
33//! - **Thread-safe**: Implementations should be `Send + Sync`
34//! - **Async-ready**: Returns `Result` for synchronous or async authentication
35//! - **Composable**: Multiple strategies can be combined via chain-of-responsibility pattern
36//!
37//! ## Example
38//!
39//! ```rust,ignore
40//! use rocketmq_auth::AuthenticationStrategy;
41//! use rocketmq_auth::AuthenticationContext;
42//! use rocketmq_auth::AuthServiceResult;
43//!
44//! struct AkSkAuthenticationStrategy;
45//!
46//! impl AuthenticationStrategy for AkSkAuthenticationStrategy {
47//! fn authenticate(&self, context: &dyn AuthenticationContext) -> AuthServiceResult<()> {
48//! // Validate access key and secret key
49//! // ...
50//! Ok(())
51//! }
52//! }
53//! ```
54
55use std::future::Future;
56use std::pin::Pin;
57
58use crate::authorization::context::authentication_context::AuthenticationContext;
59use crate::AuthServiceResult;
60
61/// Authentication strategy trait.
62///
63/// This trait defines the contract for authentication strategies in RocketMQ.
64/// Implementations of this trait provide specific authentication mechanisms
65/// (e.g., AK/SK, token-based, mTLS).
66///
67/// # Thread Safety
68///
69/// Implementations must be thread-safe (`Send + Sync`) to allow concurrent
70/// authentication requests.
71///
72/// # Object Safety
73///
74/// This trait is object-safe, meaning it can be used behind a trait object
75/// (`Box<dyn AuthenticationStrategy>` or `Arc<dyn AuthenticationStrategy>`).
76///
77/// # Error Handling
78///
79/// Authentication failures should return an `AuthServiceError` with a bounded
80/// failure kind. Never use `panic!` or `unwrap()`
81/// in production implementations.
82///
83/// # Examples
84///
85/// ## Basic Implementation
86///
87/// ```rust,ignore
88/// use rocketmq_auth::AuthenticationStrategy;
89/// use rocketmq_auth::AuthServiceResult;
90///
91/// struct AllowAllAuthenticationStrategy;
92///
93/// impl AuthenticationStrategy for AllowAllAuthenticationStrategy {
94/// fn authenticate(&self, _context: &dyn AuthenticationContext) -> AuthServiceResult<()> {
95/// // Allow all authentication attempts (for testing only!)
96/// Ok(())
97/// }
98/// }
99/// ```
100///
101/// ## AK/SK Authentication
102///
103/// ```rust,ignore
104/// use rocketmq_auth::AuthenticationStrategy;
105/// use rocketmq_auth::DefaultAuthenticationContext;
106/// use rocketmq_auth::{AuthFailureKind, AuthOperation, AuthServiceError, AuthServiceResult};
107///
108/// struct AkSkAuthenticationStrategy {
109/// user_provider: Arc<dyn UserProvider>,
110/// }
111///
112/// impl AuthenticationStrategy for AkSkAuthenticationStrategy {
113/// fn authenticate(&self, context: &dyn AuthenticationContext) -> AuthServiceResult<()> {
114/// // Downcast to DefaultAuthenticationContext
115/// let ctx = context.as_any()
116/// .downcast_ref::<DefaultAuthenticationContext>()
117/// .ok_or_else(|| AuthServiceError::new(AuthOperation::Authenticate, AuthFailureKind::InvalidInput))?;
118///
119/// // Extract username and signature
120/// let username = ctx.username()
121/// .ok_or_else(|| AuthServiceError::new(AuthOperation::Authenticate, AuthFailureKind::Unauthenticated))?;
122/// let signature = ctx.signature()
123/// .ok_or_else(|| AuthServiceError::new(AuthOperation::Authenticate, AuthFailureKind::Unauthenticated))?;
124///
125/// // Retrieve user from provider
126/// let user = self.user_provider.get_user(username.as_str())
127/// .map_err(|source| AuthServiceError::with_source(AuthOperation::Authenticate, source.kind(), source))?;
128///
129/// // Validate signature
130/// if !verify_signature(ctx.content(), user.password(), signature) {
131/// return Err(AuthServiceError::new(AuthOperation::Authenticate, AuthFailureKind::Unauthenticated));
132/// }
133///
134/// Ok(())
135/// }
136/// }
137/// ```
138pub type AuthenticationFuture<'a> = Pin<Box<dyn Future<Output = AuthServiceResult<()>> + 'a>>;
139
140pub trait AuthenticationStrategy: Send + Sync {
141 /// Authenticates a request based on the provided authentication context.
142 ///
143 /// This method performs authentication using credentials, metadata, and other
144 /// information contained in the `AuthenticationContext`. Successful authentication
145 /// returns `Ok(())`, while failures return an `AuthServiceError`.
146 ///
147 /// # Arguments
148 ///
149 /// * `context` - The authentication context containing credentials, request metadata, and any
150 /// other information required for authentication.
151 ///
152 /// # Returns
153 ///
154 /// * `Ok(())` - Authentication succeeded
155 /// * `Err(AuthServiceError)` - Authentication failed with a specific error
156 ///
157 /// # Error Semantics
158 ///
159 /// [`crate::AuthFailureKind`] provides the closed failure classification.
160 ///
161 /// # Thread Safety
162 ///
163 /// This method must be safe to call concurrently from multiple threads.
164 /// Implementations should either be stateless or use appropriate synchronization.
165 ///
166 /// # Performance Considerations
167 ///
168 /// - Minimize heap allocations in hot paths
169 /// - Consider caching authenticated sessions (see `StatefulAuthenticationStrategy`)
170 /// - Use constant-time comparison for security-critical operations
171 ///
172 /// # Examples
173 ///
174 /// ```rust,ignore
175 /// use rocketmq_auth::AuthenticationStrategy;
176 /// use rocketmq_auth::DefaultAuthenticationContext;
177 /// use rocketmq_auth::AuthServiceResult;
178 ///
179 /// fn authenticate_request(
180 /// strategy: &dyn AuthenticationStrategy,
181 /// context: &dyn AuthenticationContext,
182 /// ) -> AuthServiceResult<()> {
183 /// strategy.authenticate(context)?;
184 /// println!("Authentication succeeded");
185 /// Ok(())
186 /// }
187 /// ```
188 fn authenticate<'a>(&'a self, context: &'a dyn AuthenticationContext) -> AuthenticationFuture<'a>;
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::authentication::context::default_authentication_context::DefaultAuthenticationContext;
195 use std::sync::Arc;
196
197 /// Test implementation that always allows authentication
198 struct AllowAllAuthenticationStrategy;
199
200 impl AuthenticationStrategy for AllowAllAuthenticationStrategy {
201 fn authenticate<'a>(&'a self, _context: &'a dyn AuthenticationContext) -> AuthenticationFuture<'a> {
202 Box::pin(async { Ok(()) })
203 }
204 }
205
206 /// Test implementation that always denies authentication
207 struct DenyAllAuthenticationStrategy;
208
209 impl AuthenticationStrategy for DenyAllAuthenticationStrategy {
210 fn authenticate<'a>(&'a self, _context: &'a dyn AuthenticationContext) -> AuthenticationFuture<'a> {
211 Box::pin(async {
212 Err(crate::AuthServiceError::new(
213 crate::AuthOperation::Authenticate,
214 crate::AuthFailureKind::Unauthenticated,
215 ))
216 })
217 }
218 }
219
220 #[tokio::test]
221 async fn test_allow_all_strategy_succeeds() {
222 let strategy = AllowAllAuthenticationStrategy;
223 let context = DefaultAuthenticationContext::new();
224
225 let result = strategy.authenticate(&context).await;
226 assert!(result.is_ok());
227 }
228
229 #[tokio::test]
230 async fn test_deny_all_strategy_fails() {
231 let strategy = DenyAllAuthenticationStrategy;
232 let context = DefaultAuthenticationContext::new();
233
234 let result = strategy.authenticate(&context).await;
235 assert!(result.is_err());
236
237 let error = result.expect_err("deny strategy must reject authentication");
238 assert_eq!(error.operation(), crate::AuthOperation::Authenticate);
239 assert_eq!(error.kind(), crate::AuthFailureKind::Unauthenticated);
240 }
241
242 #[tokio::test]
243 async fn test_strategy_is_object_safe() {
244 let strategy: Arc<dyn AuthenticationStrategy> = Arc::new(AllowAllAuthenticationStrategy);
245 let context = DefaultAuthenticationContext::new();
246
247 let result = strategy.authenticate(&context).await;
248 assert!(result.is_ok());
249 }
250
251 #[test]
252 fn test_strategy_is_send_sync() {
253 fn assert_send_sync<T: Send + Sync>() {}
254 assert_send_sync::<AllowAllAuthenticationStrategy>();
255 assert_send_sync::<DenyAllAuthenticationStrategy>();
256 }
257
258 #[tokio::test]
259 async fn test_multiple_strategies_can_coexist() {
260 let allow_strategy: Arc<dyn AuthenticationStrategy> = Arc::new(AllowAllAuthenticationStrategy);
261 let deny_strategy: Arc<dyn AuthenticationStrategy> = Arc::new(DenyAllAuthenticationStrategy);
262
263 let context = DefaultAuthenticationContext::new();
264
265 assert!(allow_strategy.authenticate(&context).await.is_ok());
266 assert!(deny_strategy.authenticate(&context).await.is_err());
267 }
268}