Skip to main content

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}