rocketmq_auth/authorization/manager/metadata_manager_impl.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//! Production implementation of Authorization Metadata Manager.
16//!
17//! This module provides `AuthorizationMetadataManagerImpl`, a concrete implementation
18//! that coordinates authorization and authentication metadata providers to manage
19//! ACL (Access Control List) operations in RocketMQ's authorization system.
20//!
21//! # Architecture
22//!
23//! ```text
24//! AuthorizationMetadataManagerImpl
25//! ├── AuthorizationMetadataProvider (ACL storage)
26//! │ ├── create_acl()
27//! │ ├── update_acl()
28//! │ ├── delete_acl()
29//! │ ├── get_acl()
30//! │ └── list_acl()
31//! └── AuthenticationMetadataProvider (Subject validation)
32//! └── get_user() / validate_subject()
33//! ```
34//!
35//! # Validation Strategy
36//!
37//! All ACL mutations follow a strict validation pipeline:
38//! 1. **Structural validation**: Ensure ACL/Policy/Entry data is well-formed
39//! 2. **Subject validation**: Verify subject exists in authentication system (for USER type)
40//! 3. **Business validation**: Check IP addresses, resource patterns, actions
41//! 4. **Storage operation**: Delegate to provider after all validations pass
42//!
43//! # Error Handling
44//!
45//! - Validation errors return `AuthFailureKind::InvalidInput`
46//! - Missing subjects return `AuthFailureKind::NotFound`
47//! - Storage failures return `AuthFailureKind::Unavailable`
48//! - All errors are propagated without panic, enabling graceful degradation
49//!
50//! # Thread Safety
51//!
52//! This implementation is thread-safe through immutable references (`&self`) and
53//! `Arc`-wrapped providers that implement `Send + Sync`.
54
55use std::sync::Arc;
56
57use tracing::debug;
58
59use crate::authentication::enums::subject_type::SubjectType;
60use crate::authentication::model::subject::Subject;
61use crate::authentication::provider::AuthenticationMetadataProvider;
62use crate::authorization::enums::policy_type::PolicyType;
63use crate::authorization::metadata_provider::local::LocalAuthorizationMetadataProvider;
64#[cfg(test)]
65use crate::authorization::metadata_provider::AuthorizationMetadataProvider;
66use crate::authorization::model::acl::Acl;
67use crate::authorization::model::environment::Environment;
68use crate::authorization::model::policy::Policy;
69use crate::authorization::model::policy_entry::PolicyEntry;
70use crate::authorization::model::resource::Resource;
71use crate::config::AuthConfig;
72use crate::AuthFailureKind;
73use crate::AuthServiceError;
74use crate::AuthServiceResult;
75use crate::ProviderRegistry;
76
77/// Production implementation of Authorization Metadata Manager.
78///
79/// This struct manages ACL (Access Control List) operations by coordinating
80/// authorization and authentication metadata providers. It provides a unified,
81/// type-safe API with comprehensive validation for all ACL operations.
82///
83/// # Provider Coordination
84///
85/// - **AuthorizationMetadataProvider**: Handles ACL persistence (create, update, delete, query)
86/// - **AuthenticationMetadataProvider**: Validates subject existence for USER subjects
87///
88/// # Examples
89///
90/// ```rust,ignore
91/// use rocketmq_auth::AuthorizationMetadataManagerImpl;
92/// use rocketmq_auth::AuthConfig;
93/// use rocketmq_auth::LocalAuthorizationMetadataProvider;
94/// use std::sync::Arc;
95///
96/// // Create manager with local provider
97/// let mut provider = LocalAuthorizationMetadataProvider::new();
98/// provider.initialize(AuthConfig::default(), None)?;
99///
100/// let manager = AuthorizationMetadataManagerImpl::new(Arc::new(provider), None);
101///
102/// // Create ACL for a user
103/// let acl = Acl::of("user:alice", SubjectType::User, policy);
104/// manager.create_acl(acl).await?;
105///
106/// // Query ACL
107/// let user = UserSubject::new("alice");
108/// if let Some(acl) = manager.get_acl(&user).await? {
109/// println!("ACL found: {:?}", acl);
110/// }
111///
112/// // Cleanup
113/// manager.shutdown().await;
114/// ```
115pub struct AuthorizationMetadataManagerImpl {
116 /// Authorization metadata provider for ACL persistence.
117 ///
118 /// This provider handles all storage operations: create, read, update, delete.
119 /// Must be initialized before the manager is used.
120 ///
121 /// The handle borrows the runtime's dynamic ACL port and admission owner.
122 authorization_provider: Arc<crate::AclMetadataHandle>,
123
124 /// Authentication metadata provider used to validate USER subjects before ACL mutations.
125 authentication_provider: Option<Arc<dyn crate::UserMetadataRead>>,
126 owned_registry: Option<ProviderRegistry>,
127}
128
129impl AuthorizationMetadataManagerImpl {
130 /// Create a new Authorization Metadata Manager with providers.
131 ///
132 /// # Arguments
133 ///
134 /// * `authorization_provider` - Provider for ACL storage (required, must be initialized)
135 /// * `authentication_provider` - Optional provider for subject validation
136 ///
137 /// # Examples
138 ///
139 /// ```rust,ignore
140 /// let auth_provider = Arc::new(LocalAuthorizationMetadataProvider::new());
141 /// let manager = AuthorizationMetadataManagerImpl::new(auth_provider, None);
142 /// ```
143 pub fn new(
144 authorization_provider: Arc<LocalAuthorizationMetadataProvider>,
145 authentication_provider: Option<Arc<dyn AuthenticationMetadataProvider>>,
146 ) -> Self {
147 Self {
148 authorization_provider: crate::AclMetadataHandle::legacy(authorization_provider),
149 authentication_provider: authentication_provider.map(|port| {
150 Arc::new(crate::provider_ports::LegacyUserReader(port)) as Arc<dyn crate::UserMetadataRead>
151 }),
152 owned_registry: None,
153 }
154 }
155
156 pub fn with_registry(registry: &ProviderRegistry) -> Self {
157 Self {
158 authorization_provider: registry.authorization_metadata_provider(),
159 authentication_provider: Some(registry.authentication_metadata_provider()),
160 owned_registry: None,
161 }
162 }
163
164 /// Create a manager from configuration.
165 ///
166 /// This factory method instantiates providers based on configuration.
167 ///
168 /// # Arguments
169 ///
170 /// * `config` - Authorization configuration
171 ///
172 /// # Errors
173 ///
174 /// Returns `AuthFailureKind::InvalidConfiguration` if:
175 /// - Provider initialization fails
176 /// - Required configuration is missing
177 /// - Configuration contains invalid values
178 pub fn from_config(config: &AuthConfig) -> AuthServiceResult<Self> {
179 let registry = ProviderRegistry::local(config).map_err(|error| {
180 AuthServiceError::with_source(
181 crate::AuthOperation::Initialize,
182 crate::AuthFailureKind::InvalidConfiguration,
183 error,
184 )
185 })?;
186 let mut manager = Self::with_registry(®istry);
187 manager.owned_registry = Some(registry);
188 Ok(manager)
189 }
190
191 /// Shutdown the manager and release resources.
192 ///
193 /// This method:
194 /// - Calls shutdown on authentication provider (if present)
195 /// - Calls shutdown on authorization provider
196 /// - Flushes pending operations
197 /// - Releases system resources
198 ///
199 /// This method is idempotent (safe to call multiple times).
200 ///
201 /// # Examples
202 ///
203 /// ```rust,ignore
204 /// manager.shutdown().await;
205 /// ```
206 pub async fn shutdown(&mut self) {
207 if let Err(error) = self
208 .shutdown_until(rocketmq_runtime::ShutdownDeadline::after(
209 std::time::Duration::from_secs(5),
210 ))
211 .await
212 {
213 tracing::warn!(?error, "authorization metadata manager shutdown incomplete");
214 }
215 }
216
217 pub async fn shutdown_until(&self, deadline: rocketmq_runtime::ShutdownDeadline) -> AuthServiceResult<()> {
218 match &self.owned_registry {
219 Some(registry) => registry.shutdown_until(deadline).await,
220 None => Ok(()),
221 }
222 }
223
224 /// Create a new ACL.
225 ///
226 /// This method performs the following steps:
227 /// 1. **Validate** ACL structure (subject, policies, entries, resources, actions)
228 /// 2. **Initialize** default policy types (sets CUSTOM if not specified)
229 /// 3. **Verify subject exists** (for USER type, via authentication provider)
230 /// 4. **Check for existing ACL**:
231 /// - If exists: merge policies and update
232 /// - If not: create new ACL
233 ///
234 /// # Arguments
235 ///
236 /// * `acl` - The ACL to create
237 ///
238 /// # Errors
239 ///
240 /// - `AuthFailureKind::InvalidInput` if ACL validation fails
241 /// - `AuthFailureKind::NotFound` if subject doesn't exist
242 /// - `AuthFailureKind::Unavailable` if
243 /// storage operation fails
244 ///
245 /// # Examples
246 ///
247 /// ```rust,ignore
248 /// let acl = Acl::of("user:alice", SubjectType::User, policy);
249 /// manager.create_acl(acl).await?;
250 /// ```
251 pub async fn create_acl(&self, mut acl: Acl) -> AuthServiceResult<()> {
252 // Step 1: Validate ACL structure
253 self.validate_acl(&acl)?;
254
255 // Step 2: Initialize default policy types
256 Self::init_acl(&mut acl);
257
258 // Step 3: Verify subject exists (for USER type)
259 self.verify_subject_exists(&acl).await?;
260
261 // Step 4: Check if ACL already exists
262 let subject_key = acl.subject_key();
263 let subject_type = acl.subject_type();
264
265 let existing_acl = self
266 .authorization_provider
267 .get_acl(&SubjectWrapper {
268 key: subject_key.to_string(),
269 subject_type,
270 })
271 .await?;
272
273 match existing_acl {
274 None => {
275 // No existing ACL, create new one
276 debug!("Creating new ACL for subject: {}", subject_key);
277 self.authorization_provider.create_acl(acl).await
278 }
279 Some(mut old_acl) => {
280 // ACL exists, merge policies and update
281 debug!("ACL already exists for subject {}, merging policies", subject_key);
282 old_acl.update_policies(acl.policies().clone());
283 self.authorization_provider.update_acl(old_acl).await
284 }
285 }
286 }
287
288 /// Update an existing ACL.
289 ///
290 /// This method:
291 /// 1. Validates the ACL structure
292 /// 2. Initializes default policy types
293 /// 3. Verifies the subject exists
294 /// 4. Merges with existing ACL or creates new if not found
295 ///
296 /// # Arguments
297 ///
298 /// * `acl` - The ACL with updated policies
299 ///
300 /// # Errors
301 ///
302 /// - `AuthFailureKind::InvalidInput` if ACL validation fails
303 /// - `AuthFailureKind::NotFound` if subject doesn't exist
304 /// - `AuthFailureKind::Unavailable` if
305 /// storage operation fails
306 ///
307 /// # Examples
308 ///
309 /// ```rust,ignore
310 /// let mut acl = manager.get_acl(&user).await?.unwrap();
311 /// acl.add_policy(new_policy);
312 /// manager.update_acl(acl).await?;
313 /// ```
314 pub async fn update_acl(&self, mut acl: Acl) -> AuthServiceResult<()> {
315 // Step 1: Validate ACL
316 self.validate_acl(&acl)?;
317
318 // Step 2: Initialize default policy types
319 Self::init_acl(&mut acl);
320
321 // Step 3: Verify subject exists
322 self.verify_subject_exists(&acl).await?;
323
324 // Step 4: Get existing ACL and merge or create
325 let subject_key = acl.subject_key();
326 let subject_type = acl.subject_type();
327
328 let existing_acl = self
329 .authorization_provider
330 .get_acl(&SubjectWrapper {
331 key: subject_key.to_string(),
332 subject_type,
333 })
334 .await?;
335
336 match existing_acl {
337 None => {
338 // No existing ACL, create new one
339 debug!("No existing ACL for subject {}, creating new", subject_key);
340 self.authorization_provider.create_acl(acl).await
341 }
342 Some(mut old_acl) => {
343 // Merge policies
344 debug!("Updating existing ACL for subject: {}", subject_key);
345 old_acl.update_policies(acl.policies().clone());
346 self.authorization_provider.update_acl(old_acl).await
347 }
348 }
349 }
350
351 /// Delete an ACL for a subject.
352 ///
353 /// This is a convenience method that deletes the entire ACL.
354 ///
355 /// # Arguments
356 ///
357 /// * `subject` - The subject whose ACL should be deleted
358 ///
359 /// # Errors
360 ///
361 /// - `AuthFailureKind::NotFound` if subject doesn't exist
362 /// - `AuthFailureKind::InvalidInput` if ACL doesn't exist
363 /// - `AuthFailureKind::Unavailable` if
364 /// storage operation fails
365 ///
366 /// # Examples
367 ///
368 /// ```rust,ignore
369 /// let user = UserSubject::new("alice");
370 /// manager.delete_acl(&user).await?;
371 /// ```
372 pub async fn delete_acl<S: Subject + Send + Sync>(&self, subject: &S) -> AuthServiceResult<()> {
373 self.delete_acl_with_filter(subject, None, None).await
374 }
375
376 /// Delete an ACL or specific policy entries.
377 ///
378 /// This method provides fine-grained deletion:
379 /// - If `resource` is specified: deletes specific policy entry for that resource
380 /// - If only `policy_type` is specified: deletes entire policy of that type
381 /// - If neither specified: deletes entire ACL
382 ///
383 /// # Arguments
384 ///
385 /// * `subject` - The subject whose ACL to modify
386 /// * `policy_type` - Optional policy type filter (defaults to CUSTOM)
387 /// * `resource` - Optional resource to delete from the policy
388 ///
389 /// # Errors
390 ///
391 /// - `AuthFailureKind::NotFound` if subject doesn't exist
392 /// - `AuthFailureKind::InvalidInput` if ACL doesn't exist
393 /// - `AuthFailureKind::Unavailable` if
394 /// storage operation fails
395 ///
396 /// # Examples
397 ///
398 /// ```rust,ignore
399 /// // Delete entire ACL
400 /// manager.delete_acl_with_filter(&user, None, None).await?;
401 ///
402 /// // Delete a specific policy type
403 /// manager.delete_acl_with_filter(&user, Some(PolicyType::Custom), None).await?;
404 ///
405 /// // Delete specific resource from policy
406 /// let resource = Resource::of_topic("test-topic");
407 /// manager.delete_acl_with_filter(&user, Some(PolicyType::Custom), Some(&resource)).await?;
408 /// ```
409 pub async fn delete_acl_with_filter<S: Subject + Send + Sync>(
410 &self,
411 subject: &S,
412 policy_type: Option<PolicyType>,
413 resource: Option<&Resource>,
414 ) -> AuthServiceResult<()> {
415 // Step 1: Verify subject exists
416 self.verify_subject_by_ref(subject).await?;
417
418 // Step 2: Default policy type to CUSTOM if not specified
419 let policy_type = policy_type.unwrap_or(PolicyType::Custom);
420
421 // Step 3: Get existing ACL
422 let mut acl = self.authorization_provider.get_acl(subject).await?.ok_or_else(|| {
423 AuthServiceError::invalid_context(format!(
424 "The ACL for subject '{}' does not exist",
425 subject.subject_key()
426 ))
427 })?;
428
429 // Step 4: Delete policy entry or entire ACL
430 if let Some(resource) = resource {
431 // Delete specific resource from policy
432 debug!(
433 "Deleting resource {:?} from {:?} policy for subject {}",
434 resource,
435 policy_type,
436 subject.subject_key()
437 );
438 acl.delete_policy(policy_type, resource);
439
440 if acl.policies().is_empty() {
441 // No policies left, delete entire ACL
442 self.authorization_provider.delete_acl(subject).await
443 } else {
444 // Update ACL with remaining policies
445 self.authorization_provider.update_acl(acl).await
446 }
447 } else {
448 // Delete entire ACL
449 debug!("Deleting entire ACL for subject: {}", subject.subject_key());
450 self.authorization_provider.delete_acl(subject).await
451 }
452 }
453
454 /// Get the ACL for a subject.
455 ///
456 /// # Arguments
457 ///
458 /// * `subject` - The subject to query
459 ///
460 /// # Returns
461 ///
462 /// - `Ok(Some(Acl))` if ACL exists
463 /// - `Ok(None)` if no ACL found
464 /// - `Err(...)` if operation fails
465 ///
466 /// # Errors
467 ///
468 /// - `AuthFailureKind::NotFound` if subject doesn't exist
469 /// - `AuthFailureKind::Unavailable` if
470 /// storage operation fails
471 ///
472 /// # Examples
473 ///
474 /// ```rust,ignore
475 /// let user = UserSubject::new("alice");
476 /// if let Some(acl) = manager.get_acl(&user).await? {
477 /// println!("Found ACL with {} policies", acl.policies().len());
478 /// }
479 /// ```
480 pub async fn get_acl<S: Subject + Send + Sync>(&self, subject: &S) -> AuthServiceResult<Option<Acl>> {
481 // Step 1: Verify subject exists
482 self.verify_subject_by_ref(subject).await?;
483
484 // Step 2: Get ACL from provider
485 self.authorization_provider.get_acl(subject).await
486 }
487
488 /// List ACLs with optional filtering.
489 ///
490 /// # Arguments
491 ///
492 /// * `subject_filter` - Optional subject name filter (substring match)
493 /// * `resource_filter` - Optional resource name filter (substring match)
494 ///
495 /// # Returns
496 ///
497 /// A vector of matching ACLs. Empty vector if no matches found.
498 ///
499 /// # Errors
500 ///
501 /// - `AuthFailureKind::Unavailable` if
502 /// storage operation fails
503 ///
504 /// # Examples
505 ///
506 /// ```rust,ignore
507 /// // List all ACLs
508 /// let all_acls = manager.list_acl(None, None).await?;
509 ///
510 /// // Filter by subject
511 /// let user_acls = manager.list_acl(Some("user:"), None).await?;
512 ///
513 /// // Filter by resource
514 /// let topic_acls = manager.list_acl(None, Some("Topic:test")).await?;
515 /// ```
516 pub async fn list_acl(
517 &self,
518 subject_filter: Option<&str>,
519 resource_filter: Option<&str>,
520 ) -> AuthServiceResult<Vec<Acl>> {
521 self.authorization_provider
522 .list_acl(subject_filter, resource_filter)
523 .await
524 }
525
526 // ========== Private Validation Methods ==========
527
528 /// Initialize ACL defaults.
529 ///
530 /// Rust policies always carry an explicit `PolicyType`; no mutation is needed here.
531 fn init_acl(_acl: &mut Acl) {}
532
533 /// Validate ACL structure.
534 ///
535 /// Validates:
536 /// - Subject type is valid (not None/Unknown)
537 /// - Policies list is not empty
538 /// - Each policy is structurally valid
539 fn validate_acl(&self, acl: &Acl) -> AuthServiceResult<()> {
540 // Validate subject type (currently only User is supported)
541 // SubjectType enum doesn't have Unknown variant in Rust
542
543 // Validate policies exist
544 if acl.policies().is_empty() {
545 return Err(AuthServiceError::invalid_context("The policies is empty."));
546 }
547
548 // Validate each policy
549 for policy in acl.policies() {
550 self.validate_policy(policy)?;
551 }
552
553 Ok(())
554 }
555
556 /// Validate policy structure.
557 ///
558 /// Validates:
559 /// - Policy entries list is not empty
560 /// - Each entry is structurally valid
561 fn validate_policy(&self, policy: &Policy) -> AuthServiceResult<()> {
562 // Validate entries exist
563 if policy.entries().is_empty() {
564 return Err(AuthServiceError::invalid_context(
565 "The policy entries is empty.".to_string(),
566 ));
567 }
568
569 // Validate each entry
570 for entry in policy.entries() {
571 self.validate_policy_entry(entry)?;
572 }
573
574 Ok(())
575 }
576
577 /// Validate policy entry structure.
578 ///
579 /// Validates:
580 /// - Resource is present and valid
581 /// - Resource type is set
582 /// - Resource pattern is set
583 /// - Actions list is not empty
584 /// - Actions do not include Action::ANY
585 /// - Environment (if present) has valid IP addresses
586 /// - Decision is set
587 fn validate_policy_entry(&self, entry: &PolicyEntry) -> AuthServiceResult<()> {
588 // Validate resource
589 let resource = entry.resource();
590 // ResourceType is an enum, always has a value
591 // resource_name() returns Option<&str>, check if it's None or empty
592 if let Some(name) = resource.resource_name() {
593 if name.is_empty() {
594 return Err(AuthServiceError::invalid_context(
595 "The resource pattern is empty.".to_string(),
596 ));
597 }
598 } else {
599 return Err(AuthServiceError::invalid_context(
600 "The resource pattern is null.".to_string(),
601 ));
602 }
603
604 // Validate actions
605 if entry.actions().is_empty() {
606 return Err(AuthServiceError::invalid_context("The actions is empty."));
607 }
608
609 // Check for Action::ANY (should not be allowed in ACL entries)
610 use rocketmq_security_api::Action;
611 if entry.actions().contains(&Action::Any) {
612 return Err(AuthServiceError::invalid_context(
613 "The actions can not be Any.".to_string(),
614 ));
615 }
616
617 // Validate environment (if present)
618 if let Some(environment) = entry.environment() {
619 self.validate_environment(environment)?;
620 }
621
622 // Decision is always set in Rust (enum with no null)
623 // No validation needed
624
625 Ok(())
626 }
627
628 /// Validate environment configuration.
629 ///
630 /// Validates:
631 /// - Source IPs are not blank
632 /// - Source IPs are valid IP addresses or CIDR blocks
633 fn validate_environment(&self, environment: &Environment) -> AuthServiceResult<()> {
634 let source_ips = environment.source_ips();
635 // source_ips() returns &Vec<String>
636 if !source_ips.is_empty() {
637 for source_ip in source_ips {
638 if source_ip.trim().is_empty() {
639 return Err(AuthServiceError::invalid_context("The source ip is empty.".to_string()));
640 }
641
642 // Validate IP address or CIDR format
643 if !Self::is_valid_ip_or_cidr(source_ip) {
644 return Err(AuthServiceError::invalid_context(format!(
645 "The source ip '{}' is invalid.",
646 source_ip
647 )));
648 }
649 }
650 }
651
652 Ok(())
653 }
654
655 /// Validate if a string is a valid IP address or CIDR block.
656 ///
657 /// This is a simplified implementation. In production, consider using
658 /// the `ipnetwork` crate or similar for robust IP validation.
659 fn is_valid_ip_or_cidr(ip: &str) -> bool {
660 use std::net::IpAddr;
661
662 // Try parsing as plain IP address
663 if ip.parse::<IpAddr>().is_ok() {
664 return true;
665 }
666
667 // Try parsing as CIDR (contains '/')
668 if let Some((addr, prefix)) = ip.split_once('/') {
669 if addr.parse::<IpAddr>().is_ok() {
670 if let Ok(prefix_len) = prefix.parse::<u8>() {
671 // Validate prefix length based on IP version
672 if addr.contains(':') {
673 // IPv6
674 return prefix_len <= 128;
675 } else {
676 // IPv4
677 return prefix_len <= 32;
678 }
679 }
680 }
681 }
682
683 false
684 }
685
686 // ========== Subject Validation Methods ==========
687
688 /// Verify that the subject exists in the authentication system.
689 ///
690 /// For USER subjects, checks with authentication provider (when available).
691 /// For other subject types, assumes existence.
692 async fn verify_subject_exists(&self, acl: &Acl) -> AuthServiceResult<()> {
693 if acl.subject_type() == SubjectType::User {
694 self.verify_user_subject_key(acl.subject_key()).await?;
695 }
696
697 Ok(())
698 }
699
700 /// Verify that a subject exists by reference.
701 ///
702 /// Similar to `verify_subject_exists` but works with Subject trait references.
703 async fn verify_subject_by_ref<S: Subject + Send + Sync>(&self, subject: &S) -> AuthServiceResult<()> {
704 if subject.subject_type() == SubjectType::User {
705 self.verify_user_subject_key(subject.subject_key()).await?;
706 }
707
708 Ok(())
709 }
710
711 async fn verify_user_subject_key(&self, subject_key: &str) -> AuthServiceResult<()> {
712 let provider = self.authentication_provider.as_ref().ok_or_else(|| {
713 AuthServiceError::not_initialized("The authenticationMetadataProvider is not configured.")
714 })?;
715 let username = subject_key
716 .split_once(':')
717 .map(|(_, username)| username)
718 .unwrap_or(subject_key)
719 .trim();
720 if username.is_empty() {
721 return Err(AuthServiceError::subject_not_found(subject_key));
722 }
723
724 match provider.lookup_user(username).await {
725 Ok(_) => Ok(()),
726 Err(error) if error.kind() == AuthFailureKind::NotFound => Err(AuthServiceError::subject_not_found(
727 format!("The subject of {subject_key} is not exist."),
728 )),
729 Err(error) => Err(AuthServiceError::storage_read_failed(error)),
730 }
731 }
732}
733
734// ========== Helper Types ==========
735
736/// Wrapper struct to implement Subject trait for provider operations.
737///
738/// This is needed because we need to pass subject information to the provider's
739/// methods which expect types implementing the Subject trait.
740struct SubjectWrapper {
741 key: String,
742 subject_type: SubjectType,
743}
744
745impl Subject for SubjectWrapper {
746 fn subject_key(&self) -> &str {
747 &self.key
748 }
749
750 fn subject_type(&self) -> SubjectType {
751 self.subject_type
752 }
753}
754
755// ========== Unit Tests ==========
756
757#[cfg(test)]
758mod tests {
759 use rocketmq_security_api::Action;
760
761 use crate::authentication::enums::user_status::UserStatus;
762 use crate::authentication::enums::user_type::UserType;
763 use crate::authentication::model::user::User;
764 use crate::authentication::provider::LocalAuthenticationMetadataProvider;
765 use crate::authorization::enums::decision::Decision;
766
767 use super::*;
768 use crate::AuthFailureKind;
769
770 #[test]
771 fn test_is_valid_ip_or_cidr() {
772 // Valid IPv4
773 assert!(AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("192.168.0.1"));
774 assert!(AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("10.0.0.0"));
775
776 // Valid IPv4 CIDR
777 assert!(AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("192.168.0.0/24"));
778 assert!(AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("10.0.0.0/8"));
779
780 // Valid IPv6
781 assert!(AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("2001:db8::1"));
782 assert!(AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("::1"));
783
784 // Valid IPv6 CIDR
785 assert!(AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("2001:db8::/32"));
786
787 // Invalid IPs
788 assert!(!AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("invalid"));
789 assert!(!AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr(
790 "256.256.256.256"
791 ));
792 assert!(!AuthorizationMetadataManagerImpl::is_valid_ip_or_cidr("192.168.0.0/33"));
793 // Invalid prefix
794 }
795
796 #[tokio::test]
797 async fn create_acl_validates_user_subject_with_authentication_provider() {
798 let mut authorization_provider = LocalAuthorizationMetadataProvider::new();
799 authorization_provider
800 .initialize(AuthConfig::default(), None)
801 .expect("authorization provider should initialize");
802 let authentication_provider = Arc::new(LocalAuthenticationMetadataProvider::new());
803 let manager = AuthorizationMetadataManagerImpl::new(
804 Arc::new(authorization_provider),
805 Some(authentication_provider.clone() as Arc<dyn AuthenticationMetadataProvider>),
806 );
807 let acl = Acl::of(
808 "alice",
809 SubjectType::User,
810 Policy::of(
811 vec![Resource::of_topic("topic-a")],
812 vec![Action::Pub],
813 None,
814 Decision::Allow,
815 ),
816 );
817
818 let missing_error = manager
819 .create_acl(acl.clone())
820 .await
821 .expect_err("missing user subject should be rejected");
822 assert_eq!(missing_error.kind(), AuthFailureKind::NotFound);
823
824 let mut user = User::of_with_type("alice", "secret", UserType::Normal);
825 user.set_user_status(UserStatus::Enable);
826 authentication_provider
827 .create_user(user)
828 .await
829 .expect("user should be created");
830
831 manager
832 .create_acl(acl)
833 .await
834 .expect("existing user subject should be accepted");
835 }
836}