Skip to main content

rocketmq_auth/
layered_authorization.rs

1// Copyright 2026 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//! Adapters from policy evaluation to layered authorization decisions.
16
17use rocketmq_security_api::DetailedDecision;
18use rocketmq_security_api::LayerFailureKind;
19
20use crate::authorization::enums::decision::Decision as PolicyDecision;
21use crate::AuthFailureKind;
22use crate::AuthServiceError;
23
24/// Projects an internal policy decision into the detailed authorization contract.
25///
26/// The internal policy decision remains binary. In particular, an ACL or policy
27/// `Deny` can never become a layered `Abstain`.
28#[must_use]
29pub const fn project_policy_decision(decision: PolicyDecision) -> DetailedDecision {
30    match decision {
31        PolicyDecision::Allow => DetailedDecision::Allow,
32        PolicyDecision::Deny => DetailedDecision::Deny,
33    }
34}
35
36/// Classifies an authorization failure for the fail-closed layered contract.
37///
38/// This classification carries no underlying error text. Callers must use the
39/// fixed denial output from `rocketmq-security-api` rather than returning the
40/// source error to a peer.
41#[must_use]
42pub fn project_authorization_error(error: &AuthServiceError) -> LayerFailureKind {
43    match error.kind() {
44        AuthFailureKind::Timeout => LayerFailureKind::Timeout,
45        AuthFailureKind::InvalidConfiguration | AuthFailureKind::Unavailable => LayerFailureKind::Unavailable,
46        _ => LayerFailureKind::Error,
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::AuthOperation;
54
55    #[test]
56    fn policy_decisions_remain_binary() {
57        assert_eq!(project_policy_decision(PolicyDecision::Allow), DetailedDecision::Allow);
58        assert_eq!(project_policy_decision(PolicyDecision::Deny), DetailedDecision::Deny);
59    }
60
61    #[test]
62    fn unavailable_and_operational_errors_never_abstain() {
63        assert_eq!(
64            project_authorization_error(&AuthServiceError::new(
65                AuthOperation::Initialize,
66                AuthFailureKind::Unavailable,
67            )),
68            LayerFailureKind::Unavailable
69        );
70        assert_eq!(
71            project_authorization_error(&AuthServiceError::new(
72                AuthOperation::Authorize,
73                AuthFailureKind::Internal,
74            )),
75            LayerFailureKind::Error
76        );
77    }
78
79    #[test]
80    fn service_errors_keep_timeout_unavailable_and_error_distinct() {
81        assert_eq!(
82            project_authorization_error(&AuthServiceError::new(
83                AuthOperation::WriteMetadata,
84                AuthFailureKind::Timeout,
85            )),
86            LayerFailureKind::Timeout
87        );
88        assert_eq!(
89            project_authorization_error(&AuthServiceError::new(
90                AuthOperation::ReadMetadata,
91                AuthFailureKind::Unavailable,
92            )),
93            LayerFailureKind::Unavailable
94        );
95        assert_eq!(
96            project_authorization_error(&AuthServiceError::new(
97                AuthOperation::Authorize,
98                AuthFailureKind::Internal,
99            )),
100            LayerFailureKind::Error
101        );
102    }
103}