Skip to main content

rocketmq_auth/authorization/
metadata_provider.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//! Authorization metadata provider abstraction.
16//!
17//! This module provides the core trait for managing authorization metadata (ACLs)
18//! in RocketMQ. Implementations can store metadata in various backends such as:
19//! - Local files (YAML, JSON)
20//! - Embedded databases (RocksDB, SQLite)
21//! - Remote databases (MySQL, PostgreSQL)
22//! - Distributed configuration services (etcd, Consul)
23
24// Sub-modules
25pub mod local;
26
27use std::any::Any;
28
29// Re-export commonly used types
30pub use local::LocalAuthorizationMetadataProvider;
31
32use crate::authentication::model::subject::Subject;
33use crate::authorization::model::acl::Acl;
34use crate::config::AuthConfig;
35use crate::AuthServiceResult;
36
37/// Authorization metadata provider trait.
38///
39/// This trait defines the interface for managing Access Control List (ACL) metadata
40/// in RocketMQ's authorization system. It provides CRUD operations for ACLs and
41/// supports querying and filtering metadata.
42///
43/// # Design Principles
44///
45/// - **Async-first**: All I/O operations are async to support scalable implementations
46/// - **Flexible storage**: Implementations can use any backend (files, databases, remote services)
47/// - **Query support**: Built-in filtering capabilities for listing ACLs
48/// - **Error handling**: Comprehensive error reporting through `AuthServiceError`
49///
50/// # Thread Safety
51///
52/// Implementations must be `Send + Sync` to support concurrent access from multiple
53/// RocketMQ components (broker, namesrv, proxy).
54///
55/// # Examples
56///
57/// ```rust,ignore
58/// use rocketmq_auth::{AuthorizationMetadataProvider, AuthServiceResult};
59/// use rocketmq_auth::Acl;
60/// use rocketmq_auth::Subject;
61/// use rocketmq_auth::AuthConfig;
62///
63/// struct MyMetadataProvider {
64///     // Storage implementation
65/// }
66///
67/// impl AuthorizationMetadataProvider for MyMetadataProvider {
68///     fn initialize(
69///         &mut self,
70///         config: AuthConfig,
71///         metadata_service: Option<Box<dyn Any + Send + Sync>>,
72///     ) -> AuthServiceResult<()> {
73///         // Initialize storage backend
74///         Ok(())
75///     }
76///
77///     async fn create_acl(&self, acl: Acl) -> AuthServiceResult<()> {
78///         // Store ACL in backend
79///         Ok(())
80///     }
81///
82///     async fn get_acl<S: Subject>(&self, subject: &S) -> AuthServiceResult<Option<Acl>> {
83///         // Retrieve ACL from backend
84///         Ok(None)
85///     }
86///
87///     // ... implement other methods
88/// }
89/// ```
90#[allow(async_fn_in_trait)]
91pub trait AuthorizationMetadataProvider: Send + Sync {
92    /// Initialize the metadata provider with configuration.
93    ///
94    /// This method is called once during provider setup. Implementations should:
95    /// - Initialize storage backend (open database, connect to remote service, etc.)
96    /// - Load initial configuration and validate parameters
97    /// - Set up caching layers if needed
98    /// - Establish connection pools or prepare resources
99    ///
100    /// # Arguments
101    ///
102    /// * `config` - Authorization configuration containing provider-specific settings
103    /// * `metadata_service` - Optional metadata service for advanced integration scenarios. Can be
104    ///   downcast to implementation-specific types if needed.
105    ///
106    /// # Errors
107    ///
108    /// Returns `AuthFailureKind::InvalidConfiguration` if:
109    /// - Configuration parameters are invalid
110    /// - Storage backend cannot be initialized
111    /// - Required resources are unavailable
112    ///
113    /// # Examples
114    ///
115    /// ```rust,ignore
116    /// let mut provider = MyMetadataProvider::new();
117    /// provider.initialize(auth_config, None)?;
118    /// ```
119    fn initialize(
120        &mut self,
121        config: AuthConfig,
122        metadata_service: Option<Box<dyn Any + Send + Sync>>,
123    ) -> AuthServiceResult<()>;
124
125    /// Shutdown the metadata provider and release resources.
126    ///
127    /// This method should:
128    /// - Close database connections
129    /// - Flush pending writes
130    /// - Release file handles
131    /// - Cleanup temporary resources
132    ///
133    /// Implementations should be idempotent (safe to call multiple times).
134    fn shutdown(&mut self);
135
136    /// Create a new ACL for a subject.
137    ///
138    /// If an ACL already exists for the subject, implementations should return an error.
139    /// Use `update_acl` to modify existing ACLs.
140    ///
141    /// # Arguments
142    ///
143    /// * `acl` - The ACL to create, containing subject information and policies
144    ///
145    /// # Errors
146    ///
147    /// - `AuthFailureKind::InvalidInput` if ACL already exists
148    /// - `AuthFailureKind::Unavailable` if
149    ///   storage operation fails
150    /// - `AuthFailureKind::InvalidInput` if ACL data is malformed
151    ///
152    /// # Examples
153    ///
154    /// ```rust,ignore
155    /// let acl = Acl::of("user:alice", SubjectType::User, policy);
156    /// provider.create_acl(acl).await?;
157    /// ```
158    async fn create_acl(&self, acl: Acl) -> AuthServiceResult<()>;
159
160    /// Delete an ACL for a subject.
161    ///
162    /// If the ACL does not exist, implementations should not return an error
163    /// (idempotent operation).
164    ///
165    /// # Arguments
166    ///
167    /// * `subject` - The subject whose ACL should be deleted
168    ///
169    /// # Errors
170    ///
171    /// - `AuthFailureKind::Unavailable` if
172    ///   storage operation fails
173    ///
174    /// # Examples
175    ///
176    /// ```rust,ignore
177    /// let user = User::new("alice");
178    /// provider.delete_acl(&user).await?;
179    /// ```
180    async fn delete_acl<S: Subject + Send + Sync>(&self, subject: &S) -> AuthServiceResult<()>;
181
182    /// Update an existing ACL.
183    ///
184    /// This operation completely replaces the existing ACL. To perform partial updates,
185    /// implementations should first retrieve the ACL, modify it, then call this method.
186    ///
187    /// # Arguments
188    ///
189    /// * `acl` - The updated ACL data
190    ///
191    /// # Errors
192    ///
193    /// - `AuthFailureKind::NotFound` if ACL doesn't exist
194    /// - `AuthFailureKind::Unavailable` if
195    ///   storage operation fails
196    /// - `AuthFailureKind::InvalidInput` if ACL data is malformed
197    ///
198    /// # Examples
199    ///
200    /// ```rust,ignore
201    /// let mut acl = provider.get_acl(&user).await?.unwrap();
202    /// acl.add_policy(new_policy);
203    /// provider.update_acl(acl).await?;
204    /// ```
205    async fn update_acl(&self, acl: Acl) -> AuthServiceResult<()>;
206
207    /// Get the ACL for a specific subject.
208    ///
209    /// Returns `None` if no ACL exists for the subject.
210    ///
211    /// # Arguments
212    ///
213    /// * `subject` - The subject to query
214    ///
215    /// # Returns
216    ///
217    /// - `Ok(Some(Acl))` if ACL exists
218    /// - `Ok(None)` if no ACL found
219    /// - `Err(...)` if operation fails
220    ///
221    /// # Errors
222    ///
223    /// - `AuthFailureKind::Unavailable` if
224    ///   storage operation fails
225    ///
226    /// # Examples
227    ///
228    /// ```rust,ignore
229    /// let user = User::new("alice");
230    /// if let Some(acl) = provider.get_acl(&user).await? {
231    ///     println!("ACL found: {:?}", acl);
232    /// }
233    /// ```
234    fn get_acl<S: Subject + Send + Sync>(
235        &self,
236        subject: &S,
237    ) -> impl std::future::Future<Output = AuthServiceResult<Option<Acl>>> + Send;
238
239    /// List ACLs with optional filtering.
240    ///
241    /// This method supports filtering by subject name pattern and resource pattern.
242    /// Both filters are optional and use substring matching.
243    ///
244    /// # Arguments
245    ///
246    /// * `subject_filter` - Optional subject name filter (substring match)
247    /// * `resource_filter` - Optional resource name filter (substring match)
248    ///
249    /// # Returns
250    ///
251    /// A vector of matching ACLs. Empty vector if no matches found.
252    ///
253    /// # Errors
254    ///
255    /// - `AuthFailureKind::Unavailable` if
256    ///   storage operation fails
257    ///
258    /// # Examples
259    ///
260    /// ```rust,ignore
261    /// // List all ACLs
262    /// let all_acls = provider.list_acl(None, None).await?;
263    ///
264    /// // Filter by subject
265    /// let user_acls = provider.list_acl(Some("user:"), None).await?;
266    ///
267    /// // Filter by resource
268    /// let topic_acls = provider.list_acl(None, Some("Topic:test")).await?;
269    ///
270    /// // Both filters
271    /// let filtered = provider.list_acl(Some("user:alice"), Some("Topic:")).await?;
272    /// ```
273    async fn list_acl(
274        &self,
275        subject_filter: Option<&str>,
276        resource_filter: Option<&str>,
277    ) -> AuthServiceResult<Vec<Acl>>;
278}
279
280/// No-op authorization metadata provider for testing and disabled scenarios.
281///
282/// This provider accepts all operations but maintains no state. Useful for:
283/// - Testing authorization logic without persistence
284/// - Disabling authorization in development environments
285/// - Placeholder implementations during development
286#[derive(Debug, Default)]
287pub struct NoopMetadataProvider;
288
289impl NoopMetadataProvider {
290    /// Create a new no-op metadata provider.
291    pub fn new() -> Self {
292        Self
293    }
294}
295
296impl AuthorizationMetadataProvider for NoopMetadataProvider {
297    fn initialize(
298        &mut self,
299        _config: AuthConfig,
300        _metadata_service: Option<Box<dyn Any + Send + Sync>>,
301    ) -> AuthServiceResult<()> {
302        Ok(())
303    }
304
305    fn shutdown(&mut self) {
306        // No resources to clean up
307    }
308
309    async fn create_acl(&self, _acl: Acl) -> AuthServiceResult<()> {
310        Ok(())
311    }
312
313    async fn delete_acl<S: Subject + Send + Sync>(&self, _subject: &S) -> AuthServiceResult<()> {
314        Ok(())
315    }
316
317    async fn update_acl(&self, _acl: Acl) -> AuthServiceResult<()> {
318        Ok(())
319    }
320
321    async fn get_acl<S: Subject + Send + Sync>(&self, _subject: &S) -> AuthServiceResult<Option<Acl>> {
322        Ok(None)
323    }
324
325    async fn list_acl(
326        &self,
327        _subject_filter: Option<&str>,
328        _resource_filter: Option<&str>,
329    ) -> AuthServiceResult<Vec<Acl>> {
330        Ok(Vec::new())
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use rocketmq_security_api::Action;
337
338    use super::*;
339    use crate::authentication::enums::subject_type::SubjectType;
340    use crate::authentication::model::user::User;
341    use crate::authorization::enums::decision::Decision;
342    use crate::authorization::enums::policy_type::PolicyType;
343    use crate::authorization::model::environment::Environment;
344    use crate::authorization::model::policy::Policy;
345    use crate::authorization::model::policy_entry::PolicyEntry;
346    use crate::authorization::model::resource::Resource;
347
348    #[tokio::test]
349    async fn test_noop_provider_initialization() {
350        let mut provider = NoopMetadataProvider::new();
351        let config = AuthConfig::default();
352
353        let result = provider.initialize(config, None);
354        assert!(result.is_ok());
355    }
356
357    #[tokio::test]
358    async fn test_noop_provider_create_acl() {
359        let provider = NoopMetadataProvider::new();
360
361        let resource = Resource::of_topic("test-topic");
362        let entry = PolicyEntry::of(
363            resource,
364            vec![Action::Pub],
365            Environment::of("192.168.1.1"),
366            Decision::Allow,
367        );
368        let policy = Policy::of_entries(PolicyType::Custom, vec![entry]);
369        let acl = Acl::of("user:test", SubjectType::User, policy);
370
371        let result = provider.create_acl(acl).await;
372        assert!(result.is_ok());
373    }
374
375    #[tokio::test]
376    async fn test_noop_provider_get_acl_returns_none() {
377        let provider = NoopMetadataProvider::new();
378        let user = User::of("test");
379
380        let result = provider.get_acl(&user).await;
381        assert!(result.is_ok());
382        assert!(result.unwrap().is_none());
383    }
384
385    #[tokio::test]
386    async fn test_noop_provider_list_acl_returns_empty() {
387        let provider = NoopMetadataProvider::new();
388
389        let result = provider.list_acl(None, None).await;
390        assert!(result.is_ok());
391        assert!(result.unwrap().is_empty());
392    }
393
394    #[tokio::test]
395    async fn test_noop_provider_update_acl() {
396        let provider = NoopMetadataProvider::new();
397
398        let resource = Resource::of_topic("test-topic");
399        let entry = PolicyEntry::of(
400            resource,
401            vec![Action::Pub],
402            Environment::of("192.168.1.1"),
403            Decision::Allow,
404        );
405        let policy = Policy::of_entries(PolicyType::Custom, vec![entry]);
406        let acl = Acl::of("user:test", SubjectType::User, policy);
407
408        let result = provider.update_acl(acl).await;
409        assert!(result.is_ok());
410    }
411
412    #[tokio::test]
413    async fn test_noop_provider_delete_acl() {
414        let provider = NoopMetadataProvider::new();
415        let user = User::of("test");
416
417        let result = provider.delete_acl(&user).await;
418        assert!(result.is_ok());
419    }
420
421    #[tokio::test]
422    async fn test_noop_provider_shutdown() {
423        let mut provider = NoopMetadataProvider::new();
424        provider.shutdown();
425        // Should not panic or cause issues
426    }
427
428    #[tokio::test]
429    async fn test_noop_provider_list_with_filters() {
430        let provider = NoopMetadataProvider::new();
431
432        let result = provider.list_acl(Some("user:test"), Some("Topic:test")).await;
433        assert!(result.is_ok());
434        assert!(result.unwrap().is_empty());
435    }
436}