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}