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}