Skip to main content

rocketmq_auth/authorization/model/
acl.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
15use std::fmt;
16
17use super::policy::Policy;
18use super::resource::Resource;
19use crate::authentication::enums::subject_type::SubjectType;
20use crate::authentication::model::subject::Subject;
21use crate::authorization::enums::policy_type::PolicyType;
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct Acl {
25    subject_key: String,
26    subject_type: SubjectType,
27    policies: Vec<Policy>,
28}
29
30impl Acl {
31    /// Create from a subject (trait impl) and a single policy
32    pub fn of_subject_and_policy<S: Subject>(subject: &S, policy: Policy) -> Self {
33        Self::of_subject_and_policies(subject, vec![policy])
34    }
35
36    /// Create from a subject and a list of policies
37    pub fn of_subject_and_policies<S: Subject>(subject: &S, policies: Vec<Policy>) -> Self {
38        Acl {
39            subject_key: subject.subject_key().to_string(),
40            subject_type: subject.subject_type(),
41            policies,
42        }
43    }
44
45    /// Create from subject key/type and single policy
46    pub fn of(subject_key: impl Into<String>, subject_type: SubjectType, policy: Policy) -> Self {
47        Self::of_with_policies(subject_key, subject_type, vec![policy])
48    }
49
50    /// Create from subject key/type and policies
51    pub fn of_with_policies(subject_key: impl Into<String>, subject_type: SubjectType, policies: Vec<Policy>) -> Self {
52        Acl {
53            subject_key: normalize_subject_key(subject_key.into(), subject_type),
54            subject_type,
55            policies,
56        }
57    }
58
59    /// Update policies: if policy with same type exists -> update entries, else add it
60    pub fn update_policy(&mut self, policy: Policy) {
61        self.update_policies(vec![policy])
62    }
63
64    pub fn update_policies(&mut self, policies: Vec<Policy>) {
65        if self.policies.is_empty() {
66            self.policies = Vec::new();
67        }
68        for new_policy in policies {
69            match self.get_policy_mut(new_policy.policy_type()) {
70                None => self.policies.push(new_policy),
71                Some(old_policy) => old_policy.update_entry(new_policy.entries().clone()),
72            }
73        }
74    }
75
76    /// Delete a resource entry from the policy of the given type; remove the policy if empty
77    pub fn delete_policy(&mut self, policy_type: PolicyType, resource: &Resource) {
78        if let Some(policy) = self.get_policy_mut(policy_type) {
79            policy.delete_entry(resource);
80            if policy.entries().is_empty() {
81                // remove policy entirely
82                if let Some(pos) = self.policies.iter().position(|p| p.policy_type() == policy_type) {
83                    self.policies.remove(pos);
84                }
85            }
86        }
87    }
88
89    pub fn get_policy(&self, policy_type: PolicyType) -> Option<&Policy> {
90        self.policies.iter().find(|p| p.policy_type() == policy_type)
91    }
92
93    fn get_policy_mut(&mut self, policy_type: PolicyType) -> Option<&mut Policy> {
94        self.policies.iter_mut().find(|p| p.policy_type() == policy_type)
95    }
96
97    // getters / setters
98    pub fn subject_key(&self) -> &str {
99        &self.subject_key
100    }
101
102    pub fn subject_type(&self) -> SubjectType {
103        self.subject_type
104    }
105
106    pub fn policies(&self) -> &Vec<Policy> {
107        &self.policies
108    }
109
110    pub fn set_policies(&mut self, p: Vec<Policy>) {
111        self.policies = p;
112    }
113
114    pub fn set_subject_key(&mut self, k: impl Into<String>) {
115        self.subject_key = k.into();
116    }
117
118    pub fn set_subject_type(&mut self, t: SubjectType) {
119        self.subject_type = t;
120    }
121}
122
123fn normalize_subject_key(subject_key: String, subject_type: SubjectType) -> String {
124    if let Some((_, raw_name)) = subject_key.split_once(':') {
125        return format!("{}:{}", subject_type.name(), raw_name);
126    }
127    format!("{}:{}", subject_type.name(), subject_key)
128}
129
130impl fmt::Display for Acl {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        write!(
133            f,
134            "Acl{{subject_key={}, subject_type={}, policies_len={}}}",
135            self.subject_key,
136            self.subject_type.name(),
137            self.policies.len()
138        )
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use rocketmq_security_api::Action;
145    use rocketmq_security_api::ResourcePattern;
146    use rocketmq_security_api::ResourceType;
147
148    use super::*;
149    use crate::authentication::model::user::User;
150    use crate::authorization::enums::decision::Decision;
151    use crate::authorization::model::policy::Policy;
152
153    #[test]
154    fn test_of_and_update_delete() {
155        let user = User::of("alice");
156        let r1 = Resource::of(ResourceType::Topic, Some("t1".to_string()), ResourcePattern::Literal);
157        let r2 = Resource::of(ResourceType::Topic, Some("t2".to_string()), ResourcePattern::Literal);
158        let mut acl = Acl::of_subject_and_policies(
159            &user,
160            vec![Policy::of(vec![r1.clone()], vec![Action::Pub], None, Decision::Allow)],
161        );
162        assert_eq!(acl.policies().len(), 1);
163
164        // update: add resource r2 to the existing CUSTOM policy (entries should increase)
165        acl.update_policy(Policy::of(vec![r2.clone()], vec![Action::Pub], None, Decision::Allow));
166        assert_eq!(acl.policies().len(), 1);
167        assert_eq!(acl.policies().first().unwrap().entries().len(), 2);
168
169        // delete r1 from custom policy (entries should decrease)
170        acl.delete_policy(PolicyType::Custom, &r1);
171        assert_eq!(acl.policies().len(), 1);
172        assert_eq!(acl.policies().first().unwrap().entries().len(), 1);
173    }
174}