rocketmq_auth/authentication/factory/authentication_factory.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 Factory Implementation
16//!
17//! Central factory for creating and caching authentication components.
18//! Provides singleton-like behavior for providers and metadata managers.
19//!
20//! # Example
21//!
22//! ```rust,ignore
23//! use rocketmq_auth::authentication::factory::AuthenticationFactory;
24//! use rocketmq_auth::config::AuthConfig;
25//!
26//! let config = AuthConfig::default();
27//!
28//! // Get cached provider instance
29//! let provider = AuthenticationFactory::get_provider(&config)?;
30//!
31//! // Create authentication context from command
32//! let context = AuthenticationFactory::new_context_from_command(&config, &command)?;
33//! ```
34
35use std::any::Any;
36use std::collections::HashMap;
37use std::sync::Arc;
38use std::sync::Mutex;
39use std::sync::OnceLock;
40
41use rocketmq_error::RocketMQError;
42use rocketmq_error::RocketMQResult;
43use rocketmq_remoting::protocol::remoting_command::RemotingCommand;
44
45use crate::authentication::context::default_authentication_context::DefaultAuthenticationContext;
46use crate::authentication::manager::AuthenticationMetadataManagerImpl;
47use crate::authentication::provider::AuthenticationMetadataProvider;
48use crate::authentication::provider::AuthenticationProvider;
49use crate::authentication::provider::DefaultAuthenticationProvider;
50use crate::authorization::metadata_provider::AuthorizationMetadataProvider;
51use crate::config::AuthConfig;
52
53/// Global instance cache for authentication components
54///
55/// Thread-safe singleton cache using double-checked locking pattern.
56/// Keys are formatted as "{PREFIX}_{config_name}" to avoid collisions.
57static INSTANCE_CACHE: OnceLock<Mutex<HashMap<String, Arc<dyn Any + Send + Sync>>>> = OnceLock::new();
58
59/// Prefix for provider cache keys
60const PROVIDER_PREFIX: &str = "PROVIDER_";
61
62/// Prefix for metadata provider cache keys (currently unused in simplified version)
63#[allow(dead_code)]
64const METADATA_PROVIDER_PREFIX: &str = "METADATA_PROVIDER_";
65
66/// Authentication factory for creating and managing authentication components
67///
68/// This factory provides centralized creation logic with caching for efficiency.
69/// Instances are cached based on config name to avoid redundant creation.
70///
71/// # Thread Safety
72///
73/// All methods are thread-safe and can be called concurrently from multiple threads.
74/// The factory uses `OnceLock` and `Mutex` to ensure safe concurrent access.
75pub struct AuthenticationFactory;
76
77impl AuthenticationFactory {
78 /// Get or create an authentication provider
79 ///
80 /// Returns a cached `DefaultAuthenticationProvider` instance based on the config name.
81 /// If no instance exists for this config, creates a new one and caches it.
82 ///
83 /// # Arguments
84 ///
85 /// * `config` - Authentication configuration
86 ///
87 /// # Returns
88 ///
89 /// * `Ok(Arc<DefaultAuthenticationProvider>)` - Cached or newly created provider
90 /// * `Err(RocketMQError)` - If provider creation or caching fails
91 ///
92 /// # Example
93 ///
94 /// ```rust,ignore
95 /// let config = AuthConfig::default();
96 /// let provider = AuthenticationFactory::get_provider(&config)?;
97 /// ```
98 pub fn get_provider(config: &AuthConfig) -> RocketMQResult<Arc<DefaultAuthenticationProvider>> {
99 let key = format!("{}{}", PROVIDER_PREFIX, config.config_name);
100
101 Self::compute_if_absent(&key, || {
102 let provider = DefaultAuthenticationProvider::new();
103 Ok(Arc::new(provider) as Arc<dyn Any + Send + Sync>)
104 })
105 .and_then(|any_arc| {
106 any_arc
107 .downcast::<DefaultAuthenticationProvider>()
108 .map_err(|_| RocketMQError::illegal_argument("Failed to downcast provider"))
109 })
110 }
111
112 /// Get authentication metadata provider (simplified version)
113 ///
114 /// **Note**: In this simplified Rust implementation, this always returns `None`.
115 /// Metadata providers require async initialization, so they should be
116 /// created directly by callers instead of through this factory.
117 ///
118 /// # Arguments
119 ///
120 /// * `_config` - Authentication configuration (unused)
121 ///
122 /// # Returns
123 ///
124 /// Always returns `Ok(None)` in this implementation.
125 pub fn get_metadata_provider(
126 _config: &AuthConfig,
127 ) -> RocketMQResult<Option<Arc<dyn AuthenticationMetadataProvider>>> {
128 // Metadata providers require async initialization, so they should be
129 // created directly by callers instead of through this factory.
130 Ok(None)
131 }
132
133 /// Get authentication metadata provider with service (simplified version)
134 ///
135 /// **Note**: In this simplified Rust implementation, this always returns `None`.
136 /// Metadata providers require async initialization, so they should be
137 /// created directly by callers instead of through this factory.
138 ///
139 /// # Arguments
140 ///
141 /// * `_config` - Authentication configuration (unused)
142 /// * `_metadata_service` - Metadata service supplier (unused)
143 ///
144 /// # Returns
145 ///
146 /// Always returns `Ok(None)` in this implementation.
147 pub fn get_metadata_provider_with_service(
148 _config: &AuthConfig,
149 _metadata_service: Option<Arc<dyn Any + Send + Sync>>,
150 ) -> RocketMQResult<Option<Arc<dyn AuthenticationMetadataProvider>>> {
151 // Metadata providers require async initialization, so they should be
152 // created directly by callers instead of through this factory.
153 Ok(None)
154 }
155
156 /// Create a new authentication metadata manager
157 ///
158 /// Unlike other factory methods, this always creates a new instance (no caching).
159 /// The manager coordinates authentication and authorization metadata providers.
160 ///
161 /// # Type Parameters
162 ///
163 /// * `A` - Authentication metadata provider type
164 /// * `B` - Authorization metadata provider type
165 ///
166 /// # Arguments
167 ///
168 /// * `_config` - Authentication configuration (currently unused but kept for API compatibility)
169 /// * `auth_provider` - Optional authentication metadata provider
170 /// * `authz_provider` - Optional authorization metadata provider
171 ///
172 /// # Returns
173 ///
174 /// A new `AuthenticationMetadataManagerImpl` instance
175 pub fn get_metadata_manager<A, B>(
176 _config: &AuthConfig,
177 auth_provider: Option<A>,
178 authz_provider: Option<B>,
179 ) -> AuthenticationMetadataManagerImpl<A, B>
180 where
181 A: AuthenticationMetadataProvider + 'static,
182 B: AuthorizationMetadataProvider + 'static,
183 {
184 AuthenticationMetadataManagerImpl::new(auth_provider, authz_provider)
185 }
186
187 /// Create a new authentication context from gRPC metadata
188 ///
189 /// # Arguments
190 ///
191 /// * `config` - Authentication configuration
192 /// * `metadata` - gRPC metadata headers
193 /// * `request` - Protocol buffer request message
194 ///
195 /// # Returns
196 ///
197 /// * `Ok(Some(context))` - Successfully created context
198 /// * `Err(RocketMQError)` - If provider retrieval fails
199 pub fn new_context_from_metadata(
200 config: &AuthConfig,
201 metadata: &HashMap<String, String>,
202 request: Box<dyn Any + Send>,
203 ) -> RocketMQResult<Option<DefaultAuthenticationContext>> {
204 let provider = Self::get_provider(config)?;
205 Ok(Some(
206 <DefaultAuthenticationProvider as AuthenticationProvider>::new_context_from_metadata(
207 &provider, metadata, request,
208 ),
209 ))
210 }
211
212 /// Create a new authentication context from a remoting command
213 ///
214 /// # Arguments
215 ///
216 /// * `config` - Authentication configuration
217 /// * `command` - RocketMQ remoting command
218 ///
219 /// # Returns
220 ///
221 /// * `Ok(Some(context))` - Successfully created context
222 /// * `Err(RocketMQError)` - If provider retrieval fails
223 pub fn new_context_from_command(
224 config: &AuthConfig,
225 command: &RemotingCommand,
226 ) -> RocketMQResult<Option<DefaultAuthenticationContext>> {
227 let provider = Self::get_provider(config)?;
228 Ok(Some(
229 <DefaultAuthenticationProvider as AuthenticationProvider>::new_context_from_command(&provider, command),
230 ))
231 }
232
233 /// Internal helper for compute-if-absent pattern with double-checked locking
234 ///
235 /// This implements the singleton pattern with lazy initialization and caching.
236 /// Uses double-checked locking to avoid lock contention on cached values.
237 ///
238 /// # Arguments
239 ///
240 /// * `key` - Cache key (usually "{PREFIX}_{config_name}")
241 /// * `factory_fn` - Factory function to create the instance if not cached
242 ///
243 /// # Returns
244 ///
245 /// * `Ok(Arc<dyn Any>)` - Cached or newly created instance
246 /// * `Err(RocketMQError)` - If factory function fails or lock acquisition fails
247 ///
248 /// # Algorithm
249 ///
250 /// 1. Check cache without lock (fast path)
251 /// 2. If not found, acquire lock
252 /// 3. Double-check inside lock
253 /// 4. If still not found, call factory_fn
254 /// 5. Store in cache and return
255 fn compute_if_absent<F>(key: &str, factory_fn: F) -> RocketMQResult<Arc<dyn Any + Send + Sync>>
256 where
257 F: FnOnce() -> RocketMQResult<Arc<dyn Any + Send + Sync>>,
258 {
259 let cache = INSTANCE_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
260
261 {
262 let cache_guard = cache
263 .lock()
264 .map_err(|e| RocketMQError::illegal_argument(format!("Cache lock error: {}", e)))?;
265 if let Some(cached) = cache_guard.get(key) {
266 return Ok(Arc::clone(cached));
267 }
268 }
269
270 let mut cache_guard = cache
271 .lock()
272 .map_err(|e| RocketMQError::illegal_argument(format!("Cache lock error: {}", e)))?;
273
274 if let Some(cached) = cache_guard.get(key) {
275 return Ok(Arc::clone(cached));
276 }
277
278 let instance = factory_fn()?;
279 cache_guard.insert(key.to_string(), Arc::clone(&instance));
280 Ok(instance)
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use cheetah_string::CheetahString;
287
288 use super::*;
289
290 fn create_test_config(config_name: &str) -> AuthConfig {
291 AuthConfig {
292 config_name: CheetahString::from_string(config_name.to_string()),
293 ..AuthConfig::default()
294 }
295 }
296
297 #[test]
298 fn test_get_provider_default() {
299 let config = create_test_config("test_provider_default");
300 let provider = AuthenticationFactory::get_provider(&config);
301 assert!(provider.is_ok());
302 }
303
304 #[test]
305 fn test_get_metadata_provider_not_configured() {
306 let config = create_test_config("test_metadata_provider_none");
307 let provider = AuthenticationFactory::get_metadata_provider(&config);
308 assert!(provider.is_ok());
309 assert!(provider.unwrap().is_none());
310 }
311
312 #[test]
313 fn test_get_metadata_provider_local() {
314 let mut config = create_test_config("test_metadata_provider_local");
315 config.authentication_metadata_provider = CheetahString::from_static_str("LocalAuthenticationMetadataProvider");
316 let provider = AuthenticationFactory::get_metadata_provider(&config);
317 // In the simplified version, this always returns None because
318 // metadata providers require async initialization
319 assert!(provider.is_ok());
320 assert!(provider.unwrap().is_none());
321 }
322
323 #[test]
324 fn test_provider_caching() {
325 let config = create_test_config("test_cache");
326 let provider1 = AuthenticationFactory::get_provider(&config).unwrap();
327 let provider2 = AuthenticationFactory::get_provider(&config).unwrap();
328 assert!(Arc::ptr_eq(&provider1, &provider2));
329 }
330}