Skip to main content

rocketmq_auth/authorization/strategy/
stateless_authorization_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//! Stateless authorization strategy implementation.
16//!
17//! This strategy performs authorization evaluation on every request without caching,
18//! making it suitable for scenarios where authorization policies change frequently
19//! or where memory constraints are important.
20
21use std::any::Any;
22
23use tracing::debug;
24
25use crate::authorization::context::default_authorization_context::DefaultAuthorizationContext;
26use crate::authorization::strategy::abstract_authorization_strategy::AbstractAuthorizationStrategy;
27use crate::authorization::strategy::abstract_authorization_strategy::AuthorizationFuture;
28use crate::authorization::strategy::abstract_authorization_strategy::AuthorizationStrategy;
29use crate::authorization::strategy::evaluate_base_authorization;
30use crate::config::AuthConfig;
31use crate::AuthServiceResult;
32
33/// Stateless authorization strategy.
34///
35/// This strategy delegates all authorization decisions directly to the underlying
36/// provider without any caching. Each request is fully evaluated, ensuring that
37/// the most up-to-date policies are always applied.
38///
39/// # Use Cases
40///
41/// - High-security environments where real-time policy enforcement is critical
42/// - Systems with frequently changing authorization policies
43/// - Deployments with limited memory resources
44/// - Scenarios where authorization overhead is acceptable
45///
46/// # Trade-offs
47///
48/// **Advantages:**
49/// - Always uses the latest policies
50/// - No cache consistency issues
51/// - Lower memory footprint
52/// - Simpler implementation
53///
54/// **Disadvantages:**
55/// - Higher latency per request
56/// - Increased load on authorization provider
57/// - No performance optimization for repeated requests
58///
59/// # Examples
60///
61/// ```rust,ignore
62/// use rocketmq_auth::StatelessAuthorizationStrategy;
63/// use rocketmq_auth::AuthConfig;
64///
65/// let config = AuthConfig::default();
66/// let strategy = StatelessAuthorizationStrategy::new(config, None)?;
67///
68/// // Each evaluation queries the provider directly
69/// let context = DefaultAuthorizationContext::of(subject, resource, action, ip);
70/// match strategy.evaluate(&context).await? {
71///     AuthorizationDecision::Allow => proceed(),
72///     AuthorizationDecision::Deny(reason) => reject(reason),
73/// }
74/// ```
75pub struct StatelessAuthorizationStrategy {
76    /// Base authorization strategy implementation
77    base: AbstractAuthorizationStrategy,
78}
79
80impl StatelessAuthorizationStrategy {
81    /// Creates a new stateless authorization strategy.
82    ///
83    /// # Arguments
84    ///
85    /// * `auth_config` - Authorization configuration
86    /// * `metadata_service` - Optional metadata service for provider initialization
87    ///
88    /// # Returns
89    ///
90    /// A new `StatelessAuthorizationStrategy` instance.
91    ///
92    /// # Errors
93    ///
94    /// Returns an error if the base strategy initialization fails.
95    ///
96    /// # Examples
97    ///
98    /// ```rust,ignore
99    /// let config = AuthConfig::default();
100    /// let strategy = StatelessAuthorizationStrategy::new(config, None)?;
101    /// ```
102    pub fn new(
103        auth_config: AuthConfig,
104        metadata_service: Option<Box<dyn Any + Send + Sync>>,
105    ) -> AuthServiceResult<Self> {
106        let base = AbstractAuthorizationStrategy::new(auth_config, metadata_service)?;
107        debug!("StatelessAuthorizationStrategy initialized");
108        Ok(Self { base })
109    }
110
111    /// Gets a reference to the base authorization strategy.
112    pub fn base(&self) -> &AbstractAuthorizationStrategy {
113        &self.base
114    }
115}
116
117impl AuthorizationStrategy for StatelessAuthorizationStrategy {
118    /// Evaluates authorization for the given context without caching.
119    ///
120    /// This method delegates directly to the base `do_evaluate` method,
121    /// ensuring every request is fully evaluated.
122    ///
123    /// # Arguments
124    ///
125    /// * `context` - The authorization context to evaluate
126    ///
127    /// # Returns
128    ///
129    /// * `Ok(AuthorizationDecision)` for a final allow or deny
130    /// * `Err(AuthServiceError)` if evaluation cannot make a decision
131    ///
132    /// # Examples
133    ///
134    /// ```rust,ignore
135    /// let strategy = StatelessAuthorizationStrategy::new(config, None)?;
136    /// let context = DefaultAuthorizationContext::of(subject, resource, action, ip);
137    /// match strategy.evaluate(&context).await? {
138    ///     AuthorizationDecision::Allow => proceed(),
139    ///     AuthorizationDecision::Deny(reason) => reject(reason),
140    /// }
141    /// ```
142    fn evaluate<'a>(&'a self, context: &'a DefaultAuthorizationContext) -> AuthorizationFuture<'a> {
143        Box::pin(async move {
144            debug!("Stateless authorization evaluation started");
145            evaluate_base_authorization(&self.base, context).await
146        })
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use cheetah_string::CheetahString;
153
154    use super::*;
155
156    fn create_test_config() -> AuthConfig {
157        AuthConfig {
158            config_name: CheetahString::from("test"),
159            cluster_name: CheetahString::from("test-cluster"),
160            auth_config_path: CheetahString::from("/tmp/auth"),
161            acl_file: CheetahString::new(),
162            acl_file_watch_enabled: false,
163            acl_file_watch_interval_millis: 5_000,
164            authentication_enabled: false,
165            authentication_provider: CheetahString::new(),
166            authentication_metadata_provider: CheetahString::new(),
167            authentication_strategy: CheetahString::new(),
168            authentication_whitelist: CheetahString::new(),
169            init_authentication_user: CheetahString::new(),
170            inner_client_authentication_credentials: CheetahString::new(),
171            signature_algorithm: Default::default(),
172            request_timestamp_expired_millis: 0,
173            authorization_enabled: true,
174            authorization_provider: CheetahString::new(),
175            authorization_metadata_provider: CheetahString::new(),
176            authorization_strategy: CheetahString::new(),
177            authorization_whitelist: CheetahString::from("10,11"),
178            migrate_auth_from_v1_enabled: false,
179            user_cache_max_num: 1000,
180            user_cache_expired_second: 300,
181            user_cache_refresh_second: 60,
182            acl_cache_max_num: 1000,
183            acl_cache_expired_second: 300,
184            acl_cache_refresh_second: 60,
185            stateful_authentication_cache_max_num: 1000,
186            stateful_authentication_cache_expired_second: 300,
187            stateful_authorization_cache_max_num: 1000,
188            stateful_authorization_cache_expired_second: 300,
189            stateful_authorization_cache_negative_enable: false,
190            ..AuthConfig::default()
191        }
192    }
193
194    #[test]
195    fn test_stateless_strategy_creation() {
196        let config = create_test_config();
197        let strategy = StatelessAuthorizationStrategy::new(config, None);
198        assert!(strategy.is_ok());
199    }
200
201    #[test]
202    fn test_stateless_strategy_whitelist() {
203        let config = create_test_config();
204        let strategy = StatelessAuthorizationStrategy::new(config, None).unwrap();
205
206        assert!(strategy.base().is_whitelisted("10"));
207        assert!(strategy.base().is_whitelisted("11"));
208        assert!(!strategy.base().is_whitelisted("12"));
209    }
210
211    #[tokio::test]
212    async fn test_stateless_evaluate_disabled() {
213        let mut config = create_test_config();
214        config.authorization_enabled = false;
215
216        let strategy = StatelessAuthorizationStrategy::new(config, None).unwrap();
217        let context = DefaultAuthorizationContext::default();
218
219        let result = strategy.evaluate(&context).await;
220        assert!(result.is_ok());
221    }
222
223    #[tokio::test(flavor = "current_thread")]
224    async fn evaluate_inside_current_thread_runtime_without_nested_block_on_panic() {
225        let mut config = create_test_config();
226        config.authorization_enabled = false;
227        let strategy = StatelessAuthorizationStrategy::new(config, None).unwrap();
228        let context = DefaultAuthorizationContext::default();
229        assert!(strategy.evaluate(&context).await.is_ok());
230    }
231}