Skip to main content

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}