Skip to main content

rocketmq_auth/authorization/chain/
handler_chain.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 handler chain implementation.
16//!
17//! This module provides a chain of authorization handlers that execute sequentially
18//! until one grants access, all deny it, or an operational failure stops evaluation.
19
20use std::sync::Arc;
21
22use rocketmq_security_api::AuthorizationDecision;
23#[cfg(test)]
24use rocketmq_security_api::AuthorizationDenial;
25
26use super::handler::AuthorizationHandler;
27use crate::authorization::context::default_authorization_context::DefaultAuthorizationContext;
28use crate::AuthServiceError;
29use crate::AuthServiceResult;
30
31/// Authorization handler chain.
32///
33/// Executes handlers in sequence with fail-closed semantics. An allow grants
34/// access, ordinary denials may be followed by another authorization source,
35/// and operational errors stop evaluation immediately.
36///
37/// # Example
38///
39/// ```rust,ignore
40/// use rocketmq_auth::{AclAuthorizationHandler, AuthorizationHandlerChain};
41/// use std::sync::Arc;
42///
43/// let chain = AuthorizationHandlerChain::new()
44///     .add_handler(Arc::new(AclAuthorizationHandler::new(provider)));
45///
46/// // Execute the chain and consume both normal outcomes.
47/// match chain.handle(&context).await? {
48///     AuthorizationDecision::Allow => proceed(),
49///     AuthorizationDecision::Deny(reason) => reject(reason),
50/// }
51/// ```
52pub struct AuthorizationHandlerChain {
53    handlers: Vec<Arc<dyn AuthorizationHandler>>,
54}
55
56impl AuthorizationHandlerChain {
57    /// Create an empty handler chain.
58    pub fn new() -> Self {
59        Self { handlers: Vec::new() }
60    }
61
62    /// Add a handler to the end of the chain.
63    pub fn add_handler(mut self, handler: Arc<dyn AuthorizationHandler>) -> Self {
64        self.handlers.push(handler);
65        self
66    }
67
68    /// Execute the authorization chain.
69    ///
70    /// Handlers are executed in order until one allows the request, every
71    /// handler denies it, or an operational failure occurs.
72    ///
73    /// # Returns
74    ///
75    /// - `Ok(AuthorizationDecision)` for a final allow or deny
76    /// - `Err(AuthServiceError)` when evaluation cannot produce a decision
77    pub async fn handle(&self, context: &DefaultAuthorizationContext) -> AuthServiceResult<AuthorizationDecision> {
78        if self.handlers.is_empty() {
79            return Err(AuthServiceError::not_initialized(
80                "no authorization handlers configured".to_owned(),
81            ));
82        }
83
84        let mut denial = None;
85        for handler in &self.handlers {
86            match handler.handle(context).await {
87                Ok(AuthorizationDecision::Allow) => return Ok(AuthorizationDecision::Allow),
88                Ok(AuthorizationDecision::Deny(reason)) => denial.get_or_insert(reason),
89                Err(error) => return Err(error),
90            };
91        }
92
93        denial.map_or_else(
94            || {
95                Err(AuthServiceError::not_initialized(
96                    "authorization handler chain did not produce a decision".to_owned(),
97                ))
98            },
99            |reason| Ok(AuthorizationDecision::Deny(reason)),
100        )
101    }
102
103    /// Get the number of handlers in the chain.
104    pub fn len(&self) -> usize {
105        self.handlers.len()
106    }
107
108    /// Check if the chain is empty.
109    pub fn is_empty(&self) -> bool {
110        self.handlers.is_empty()
111    }
112}
113
114impl Default for AuthorizationHandlerChain {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use std::future::Future;
123    use std::pin::Pin;
124    use std::sync::atomic::AtomicUsize;
125    use std::sync::atomic::Ordering;
126    use std::sync::Arc;
127
128    use rocketmq_security_api::Action;
129
130    use super::*;
131    use crate::authentication::enums::subject_type::SubjectType;
132    use crate::authorization::chain::handler::AuthorizationHandler;
133    use crate::authorization::context::default_authorization_context::DefaultAuthorizationContext;
134    use crate::authorization::model::resource::Resource;
135
136    struct AllowHandler {
137        call_count: Arc<AtomicUsize>,
138    }
139
140    impl AuthorizationHandler for AllowHandler {
141        fn handle<'a>(
142            &'a self,
143            _context: &'a DefaultAuthorizationContext,
144        ) -> Pin<Box<dyn Future<Output = AuthServiceResult<AuthorizationDecision>> + Send + 'a>> {
145            self.call_count.fetch_add(1, Ordering::SeqCst);
146            Box::pin(async move { Ok(AuthorizationDecision::Allow) })
147        }
148    }
149
150    struct DenyHandler {
151        call_count: Arc<AtomicUsize>,
152    }
153
154    impl AuthorizationHandler for DenyHandler {
155        fn handle<'a>(
156            &'a self,
157            _context: &'a DefaultAuthorizationContext,
158        ) -> Pin<Box<dyn Future<Output = AuthServiceResult<AuthorizationDecision>> + Send + 'a>> {
159            self.call_count.fetch_add(1, Ordering::SeqCst);
160            Box::pin(async move { Ok(AuthorizationDecision::Deny(AuthorizationDenial::PermissionDenied)) })
161        }
162    }
163
164    struct FailureHandler {
165        call_count: Arc<AtomicUsize>,
166    }
167
168    impl AuthorizationHandler for FailureHandler {
169        fn handle<'a>(
170            &'a self,
171            _context: &'a DefaultAuthorizationContext,
172        ) -> Pin<Box<dyn Future<Output = AuthServiceResult<AuthorizationDecision>> + Send + 'a>> {
173            self.call_count.fetch_add(1, Ordering::SeqCst);
174            Box::pin(async move { Err(AuthServiceError::invalid_context("broken context".to_owned())) })
175        }
176    }
177
178    #[tokio::test]
179    async fn test_chain_single_handler_allow() {
180        let counter = Arc::new(AtomicUsize::new(0));
181        let handler = Arc::new(AllowHandler {
182            call_count: counter.clone(),
183        });
184
185        let chain = AuthorizationHandlerChain::new().add_handler(handler);
186
187        let context = DefaultAuthorizationContext::of(
188            "user",
189            SubjectType::User,
190            Resource::of_topic("test"),
191            Action::Pub,
192            "127.0.0.1",
193        );
194
195        let result = chain.handle(&context).await;
196        assert_eq!(result.unwrap(), AuthorizationDecision::Allow);
197        assert_eq!(counter.load(Ordering::SeqCst), 1);
198    }
199
200    #[tokio::test]
201    async fn test_chain_single_handler_deny() {
202        let counter = Arc::new(AtomicUsize::new(0));
203        let handler = Arc::new(DenyHandler {
204            call_count: counter.clone(),
205        });
206
207        let chain = AuthorizationHandlerChain::new().add_handler(handler);
208
209        let context = DefaultAuthorizationContext::of(
210            "user",
211            SubjectType::User,
212            Resource::of_topic("test"),
213            Action::Pub,
214            "127.0.0.1",
215        );
216
217        let result = chain.handle(&context).await;
218        assert_eq!(
219            result.unwrap(),
220            AuthorizationDecision::Deny(AuthorizationDenial::PermissionDenied)
221        );
222        assert_eq!(counter.load(Ordering::SeqCst), 1);
223    }
224
225    #[tokio::test]
226    async fn test_chain_multiple_handlers_first_allow() {
227        let counter1 = Arc::new(AtomicUsize::new(0));
228        let counter2 = Arc::new(AtomicUsize::new(0));
229
230        let chain = AuthorizationHandlerChain::new()
231            .add_handler(Arc::new(AllowHandler {
232                call_count: counter1.clone(),
233            }))
234            .add_handler(Arc::new(DenyHandler {
235                call_count: counter2.clone(),
236            }));
237
238        let context = DefaultAuthorizationContext::of(
239            "user",
240            SubjectType::User,
241            Resource::of_topic("test"),
242            Action::Pub,
243            "127.0.0.1",
244        );
245
246        let result = chain.handle(&context).await;
247        assert_eq!(result.unwrap(), AuthorizationDecision::Allow);
248        assert_eq!(counter1.load(Ordering::SeqCst), 1);
249        assert_eq!(counter2.load(Ordering::SeqCst), 0); // Should not be called
250    }
251
252    #[tokio::test]
253    async fn test_chain_deny_can_be_followed_by_allow() {
254        let counter1 = Arc::new(AtomicUsize::new(0));
255        let counter2 = Arc::new(AtomicUsize::new(0));
256
257        let chain = AuthorizationHandlerChain::new()
258            .add_handler(Arc::new(DenyHandler {
259                call_count: counter1.clone(),
260            }))
261            .add_handler(Arc::new(AllowHandler {
262                call_count: counter2.clone(),
263            }));
264
265        let context = DefaultAuthorizationContext::of(
266            "user",
267            SubjectType::User,
268            Resource::of_topic("test"),
269            Action::Pub,
270            "127.0.0.1",
271        );
272
273        let result = chain.handle(&context).await;
274        assert_eq!(result.unwrap(), AuthorizationDecision::Allow);
275        assert_eq!(counter1.load(Ordering::SeqCst), 1);
276        assert_eq!(counter2.load(Ordering::SeqCst), 1);
277    }
278
279    #[tokio::test]
280    async fn test_chain_all_deny() {
281        let counter1 = Arc::new(AtomicUsize::new(0));
282        let counter2 = Arc::new(AtomicUsize::new(0));
283
284        let chain = AuthorizationHandlerChain::new()
285            .add_handler(Arc::new(DenyHandler {
286                call_count: counter1.clone(),
287            }))
288            .add_handler(Arc::new(DenyHandler {
289                call_count: counter2.clone(),
290            }));
291
292        let context = DefaultAuthorizationContext::of(
293            "user",
294            SubjectType::User,
295            Resource::of_topic("test"),
296            Action::Pub,
297            "127.0.0.1",
298        );
299
300        let result = chain.handle(&context).await;
301        assert_eq!(
302            result.unwrap(),
303            AuthorizationDecision::Deny(AuthorizationDenial::PermissionDenied)
304        );
305        assert_eq!(counter1.load(Ordering::SeqCst), 1);
306        assert_eq!(counter2.load(Ordering::SeqCst), 1);
307    }
308
309    #[tokio::test]
310    async fn test_chain_operational_failure_is_terminal() {
311        let failure_count = Arc::new(AtomicUsize::new(0));
312        let allow_count = Arc::new(AtomicUsize::new(0));
313        let chain = AuthorizationHandlerChain::new()
314            .add_handler(Arc::new(FailureHandler {
315                call_count: failure_count.clone(),
316            }))
317            .add_handler(Arc::new(AllowHandler {
318                call_count: allow_count.clone(),
319            }));
320        let context = DefaultAuthorizationContext::default();
321
322        let error = chain.handle(&context).await.unwrap_err();
323        assert_eq!(error.kind(), crate::AuthFailureKind::InvalidInput);
324        assert_eq!(failure_count.load(Ordering::SeqCst), 1);
325        assert_eq!(allow_count.load(Ordering::SeqCst), 0);
326    }
327
328    #[tokio::test]
329    async fn test_empty_chain() {
330        let chain = AuthorizationHandlerChain::new();
331
332        let context = DefaultAuthorizationContext::of(
333            "user",
334            SubjectType::User,
335            Resource::of_topic("test"),
336            Action::Pub,
337            "127.0.0.1",
338        );
339
340        let result = chain.handle(&context).await;
341        assert!(result.is_err());
342    }
343}