rocketmq_auth/authorization/chain/
acl_authorization_handler.rs1use std::cmp::Ordering;
26use std::future::Future;
27use std::pin::Pin;
28use std::sync::Arc;
29
30use rocketmq_security_api::AuthorizationDecision;
31use rocketmq_security_api::AuthorizationDenial;
32use rocketmq_security_api::ResourcePattern;
33use rocketmq_security_api::ResourceType;
34
35use super::handler::AuthorizationHandler;
36use crate::authorization::context::default_authorization_context::DefaultAuthorizationContext;
37use crate::authorization::enums::decision::Decision;
38use crate::authorization::enums::policy_type::PolicyType;
39#[cfg(test)]
40use crate::authorization::metadata_provider::AuthorizationMetadataProvider;
41use crate::authorization::model::acl::Acl;
42use crate::authorization::model::environment::Environment;
43use crate::authorization::model::policy::Policy;
44use crate::authorization::model::policy_entry::PolicyEntry;
45use crate::AclMetadataRead;
46use crate::AuthServiceError;
47use crate::AuthServiceResult;
48use crate::SubjectKey;
49
50pub struct AclAuthorizationHandler<P: AclMetadataRead + ?Sized> {
65 metadata_provider: Arc<P>,
67}
68
69impl<P: AclMetadataRead + ?Sized> AclAuthorizationHandler<P> {
70 pub fn new(metadata_provider: Arc<P>) -> Self {
87 Self { metadata_provider }
88 }
89
90 async fn match_policy_entries(&self, context: &DefaultAuthorizationContext, acl: &Acl) -> Option<PolicyEntry> {
98 let mut matched_entries = Vec::new();
99
100 for policy in acl
104 .policies()
105 .iter()
106 .filter(|policy| policy.policy_type() == PolicyType::Custom)
107 {
108 if let Some(mut entries) = self.match_policy_entries_from_policy(context, policy) {
109 matched_entries.append(&mut entries);
110 }
111 }
112
113 if matched_entries.is_empty() {
115 for policy in acl
116 .policies()
117 .iter()
118 .filter(|policy| policy.policy_type() == PolicyType::Default)
119 {
120 if let Some(mut entries) = self.match_policy_entries_from_policy(context, policy) {
121 matched_entries.append(&mut entries);
122 }
123 }
124 }
125
126 if matched_entries.is_empty() {
128 return None;
129 }
130
131 matched_entries.sort_by(|a, b| self.compare_policy_entries(a, b));
133
134 matched_entries.into_iter().next()
136 }
137
138 fn match_policy_entries_from_policy(
149 &self,
150 context: &DefaultAuthorizationContext,
151 policy: &Policy,
152 ) -> Option<Vec<PolicyEntry>> {
153 let entries = policy.entries();
154 if entries.is_empty() {
155 return None;
156 }
157
158 let resource_binding = context.resource();
159 let resource = resource_binding.as_ref()?;
160 let actions = context.actions();
161 let source_ip_binding = context.source_ip();
162 let source_ip = source_ip_binding.unwrap_or("");
163 let environment = Environment::of(source_ip).unwrap_or_default();
164
165 let matched: Vec<PolicyEntry> = entries
166 .iter()
167 .filter(|entry| entry.is_match_resource(resource))
168 .filter(|entry| entry.is_match_action(actions))
169 .filter(|entry| entry.is_match_environment(&environment))
170 .cloned()
171 .collect();
172
173 if matched.is_empty() {
174 None
175 } else {
176 Some(matched)
177 }
178 }
179
180 fn compare_policy_entries(&self, o1: &PolicyEntry, o2: &PolicyEntry) -> Ordering {
193 let r1 = o1.resource();
194 let r2 = o2.resource();
195
196 if r1.resource_type != r2.resource_type {
198 if r1.resource_type == ResourceType::Any {
200 return Ordering::Greater; }
202 if r2.resource_type == ResourceType::Any {
203 return Ordering::Less; }
205 }
206
207 if r1.resource_pattern != r2.resource_pattern {
209 return match (r1.resource_pattern, r2.resource_pattern) {
210 (ResourcePattern::Literal, _) => Ordering::Less, (_, ResourcePattern::Literal) => Ordering::Greater,
212 (ResourcePattern::Prefixed, ResourcePattern::Any) => Ordering::Less,
213 (ResourcePattern::Any, ResourcePattern::Prefixed) => Ordering::Greater,
214 _ => Ordering::Equal,
215 };
216 }
217
218 if r1.resource_pattern == ResourcePattern::Prefixed {
220 if let (Some(name1), Some(name2)) = (&r1.resource_name, &r2.resource_name) {
221 let cmp = name2.len().cmp(&name1.len()); if cmp != Ordering::Equal {
223 return cmp;
224 }
225 }
226 }
227
228 let d1 = o1.decision();
230 let d2 = o2.decision();
231 if d1 != d2 {
232 return if d1 == Decision::Deny {
233 Ordering::Less } else {
235 Ordering::Greater };
237 }
238
239 Ordering::Equal
240 }
241}
242
243impl<P: AclMetadataRead + ?Sized + 'static> AuthorizationHandler for AclAuthorizationHandler<P> {
244 fn handle<'a>(
245 &'a self,
246 context: &'a DefaultAuthorizationContext,
247 ) -> Pin<Box<dyn Future<Output = AuthServiceResult<AuthorizationDecision>> + Send + 'a>> {
248 Box::pin(async move {
249 let subject_binding = context.subject();
251 let subject_wrapper = subject_binding
252 .as_ref()
253 .ok_or_else(|| AuthServiceError::invalid_context("subject is missing".to_owned()))?;
254
255 let subject = SubjectKey::new(subject_wrapper.subject_type(), subject_wrapper.subject_key());
256
257 let acl = self.metadata_provider.lookup_acl(&subject).await?;
259 let Some(acl) = acl else {
260 return Ok(AuthorizationDecision::Deny(AuthorizationDenial::SubjectUnknown));
261 };
262
263 let Some(matched_entry) = self.match_policy_entries(context, &acl).await else {
265 return Ok(AuthorizationDecision::Deny(AuthorizationDenial::PolicyNotApplicable));
266 };
267
268 if matched_entry.decision() == Decision::Deny {
270 return Ok(AuthorizationDecision::Deny(AuthorizationDenial::PermissionDenied));
271 }
272
273 Ok(AuthorizationDecision::Allow)
275 })
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use std::sync::Arc;
282
283 use rocketmq_security_api::Action;
284 use rocketmq_security_api::ResourcePattern;
285 use rocketmq_security_api::ResourceType;
286
287 use super::*;
288 use crate::authentication::enums::subject_type::SubjectType;
289 use crate::authentication::model::user::User;
290 use crate::authorization::context::default_authorization_context::DefaultAuthorizationContext;
291 use crate::authorization::metadata_provider::local::LocalAuthorizationMetadataProvider;
292 use crate::authorization::model::policy::Policy;
293 use crate::authorization::model::resource::Resource;
294 use crate::config::AuthConfig;
295
296 #[tokio::test]
297 async fn test_acl_authorization_handler_allow() {
298 let mut provider = LocalAuthorizationMetadataProvider::new();
300 provider.initialize(AuthConfig::default(), None).unwrap();
301 let provider = Arc::new(provider);
302
303 let user = User::of("alice");
305 let resource = Resource::of_topic("test-topic");
306 let policy = Policy::of(vec![resource.clone()], vec![Action::Pub], None, Decision::Allow);
307 let acl = Acl::of_subject_and_policy(&user, policy);
308
309 provider.create_acl(acl).await.unwrap();
311
312 let handler = AclAuthorizationHandler::new(provider);
314
315 let context = DefaultAuthorizationContext::of("alice", SubjectType::User, resource, Action::Pub, "127.0.0.1");
317
318 let result = handler.handle(&context).await;
320 assert_eq!(result.unwrap(), AuthorizationDecision::Allow);
321 }
322
323 #[tokio::test]
324 async fn test_acl_authorization_handler_deny() {
325 let mut provider = LocalAuthorizationMetadataProvider::new();
326 provider.initialize(AuthConfig::default(), None).unwrap();
327 let provider = Arc::new(provider);
328
329 let user = User::of("bob");
331 let resource = Resource::of_topic("forbidden-topic");
332 let policy = Policy::of(vec![resource.clone()], vec![Action::Pub], None, Decision::Deny);
333 let acl = Acl::of_subject_and_policy(&user, policy);
334
335 provider.create_acl(acl).await.unwrap();
336
337 let handler = AclAuthorizationHandler::new(provider);
338
339 let context = DefaultAuthorizationContext::of("bob", SubjectType::User, resource, Action::Pub, "192.168.1.1");
340
341 let result = handler.handle(&context).await;
343 assert_eq!(
344 result.unwrap(),
345 AuthorizationDecision::Deny(AuthorizationDenial::PermissionDenied)
346 );
347 }
348
349 #[tokio::test]
350 async fn test_acl_authorization_handler_no_acl() {
351 let mut provider = LocalAuthorizationMetadataProvider::new();
352 provider.initialize(AuthConfig::default(), None).unwrap();
353 let provider = Arc::new(provider);
354 let handler = AclAuthorizationHandler::new(provider);
355
356 let resource = Resource::of_topic("test-topic");
357 let context = DefaultAuthorizationContext::of("charlie", SubjectType::User, resource, Action::Pub, "10.0.0.1");
358
359 let result = handler.handle(&context).await;
361 assert_eq!(
362 result.unwrap(),
363 AuthorizationDecision::Deny(AuthorizationDenial::SubjectUnknown)
364 );
365 }
366
367 #[tokio::test]
368 async fn test_acl_authorization_handler_no_matching_policy() {
369 let mut provider = LocalAuthorizationMetadataProvider::new();
370 provider.initialize(AuthConfig::default(), None).unwrap();
371 let provider = Arc::new(provider);
372
373 let user = User::of("dave");
375 let allowed_resource = Resource::of_topic("topic-a");
376 let policy = Policy::of(vec![allowed_resource], vec![Action::Pub], None, Decision::Allow);
377 let acl = Acl::of_subject_and_policy(&user, policy);
378
379 provider.create_acl(acl).await.unwrap();
380
381 let handler = AclAuthorizationHandler::new(provider);
382
383 let requested_resource = Resource::of_topic("topic-b");
384 let context =
385 DefaultAuthorizationContext::of("dave", SubjectType::User, requested_resource, Action::Pub, "172.16.0.1");
386
387 let result = handler.handle(&context).await;
389 assert_eq!(
390 result.unwrap(),
391 AuthorizationDecision::Deny(AuthorizationDenial::PolicyNotApplicable)
392 );
393 }
394
395 #[test]
396 fn test_compare_policy_entries_resource_type() {
397 let provider = Arc::new(LocalAuthorizationMetadataProvider::new());
398 let handler = AclAuthorizationHandler::new(provider);
399
400 let topic_entry = PolicyEntry::of(
401 Resource::of(ResourceType::Topic, Some("t1".to_string()), ResourcePattern::Literal),
402 vec![Action::Pub],
403 None,
404 Decision::Allow,
405 );
406
407 let any_entry = PolicyEntry::of(
408 Resource::of(ResourceType::Any, None, ResourcePattern::Any),
409 vec![Action::Pub],
410 None,
411 Decision::Allow,
412 );
413
414 assert_eq!(handler.compare_policy_entries(&topic_entry, &any_entry), Ordering::Less);
416 assert_eq!(
417 handler.compare_policy_entries(&any_entry, &topic_entry),
418 Ordering::Greater
419 );
420 }
421
422 #[test]
423 fn test_compare_policy_entries_resource_pattern() {
424 let provider = Arc::new(LocalAuthorizationMetadataProvider::new());
425 let handler = AclAuthorizationHandler::new(provider);
426
427 let literal = PolicyEntry::of(
428 Resource::of(ResourceType::Topic, Some("test".to_string()), ResourcePattern::Literal),
429 vec![Action::Pub],
430 None,
431 Decision::Allow,
432 );
433
434 let prefixed = PolicyEntry::of(
435 Resource::of(ResourceType::Topic, Some("test".to_string()), ResourcePattern::Prefixed),
436 vec![Action::Pub],
437 None,
438 Decision::Allow,
439 );
440
441 assert_eq!(handler.compare_policy_entries(&literal, &prefixed), Ordering::Less);
443 }
444
445 #[test]
446 fn test_compare_policy_entries_decision() {
447 let provider = Arc::new(LocalAuthorizationMetadataProvider::new());
448 let handler = AclAuthorizationHandler::new(provider);
449
450 let deny = PolicyEntry::of(
451 Resource::of(ResourceType::Topic, Some("t1".to_string()), ResourcePattern::Literal),
452 vec![Action::Pub],
453 None,
454 Decision::Deny,
455 );
456
457 let allow = PolicyEntry::of(
458 Resource::of(ResourceType::Topic, Some("t1".to_string()), ResourcePattern::Literal),
459 vec![Action::Pub],
460 None,
461 Decision::Allow,
462 );
463
464 assert_eq!(handler.compare_policy_entries(&deny, &allow), Ordering::Less);
466 }
467}