Skip to main content

rocketmq_auth/authorization/model/
resource.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 rocketmq_common::common::mix_all;
18use rocketmq_common::common::resource::resource_pattern::ResourcePattern;
19use rocketmq_common::common::resource::resource_type::ResourceType;
20use rocketmq_remoting::protocol::namespace_util::NamespaceUtil;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct Resource {
24    pub resource_type: ResourceType,
25    pub resource_name: Option<String>,
26    pub resource_pattern: ResourcePattern,
27}
28
29impl Resource {
30    pub fn of_cluster(cluster_name: &str) -> Self {
31        Self::of(
32            ResourceType::Cluster,
33            Some(cluster_name.to_string()),
34            ResourcePattern::Literal,
35        )
36    }
37
38    pub fn of_topic(topic_name: &str) -> Self {
39        Self::of(
40            ResourceType::Topic,
41            Some(topic_name.to_string()),
42            ResourcePattern::Literal,
43        )
44    }
45
46    pub fn of_group(mut group_name: String) -> Self {
47        if NamespaceUtil::is_retry_topic(&group_name) {
48            // strip retry prefix
49            let prefix = mix_all::RETRY_GROUP_TOPIC_PREFIX;
50            if group_name.starts_with(prefix) {
51                group_name = group_name[prefix.len()..].to_string();
52            }
53        }
54        Self::of(ResourceType::Group, Some(group_name), ResourcePattern::Literal)
55    }
56
57    pub fn of(resource_type: ResourceType, resource_name: Option<String>, resource_pattern: ResourcePattern) -> Self {
58        Resource {
59            resource_type,
60            resource_name,
61            resource_pattern,
62        }
63    }
64
65    pub fn of_list<I, S>(resource_keys: I) -> Option<Vec<Self>>
66    where
67        I: IntoIterator<Item = S>,
68        S: AsRef<str>,
69    {
70        let mut out = Vec::new();
71        for s in resource_keys.into_iter() {
72            let sref = s.as_ref();
73            if let Some(r) = Resource::of_str(sref) {
74                out.push(r);
75            }
76        }
77        if out.is_empty() {
78            None
79        } else {
80            Some(out)
81        }
82    }
83
84    pub fn of_str(resource_key: &str) -> Option<Self> {
85        let key = resource_key.trim();
86        if key.is_empty() {
87            return None;
88        }
89        if key == "*" {
90            return Some(Self::of(ResourceType::Any, None, ResourcePattern::Any));
91        }
92        let (t, name) = match key.split_once(':') {
93            Some((a, b)) => (a, b),
94            None => (key, ""),
95        };
96        let resource_type = ResourceType::get_by_name(t)?;
97        let mut resource_name = if name.is_empty() { None } else { Some(name.to_string()) };
98        let mut resource_pattern = ResourcePattern::Literal;
99
100        if let Some(ref rn) = resource_name {
101            if rn == "*" {
102                resource_name = None;
103                resource_pattern = ResourcePattern::Any;
104            } else if rn.ends_with('*') {
105                let trimmed = rn.trim_end_matches('*').to_string();
106                resource_name = Some(trimmed);
107                resource_pattern = ResourcePattern::Prefixed;
108            }
109        }
110
111        Some(Self::of(resource_type, resource_name, resource_pattern))
112    }
113
114    /// Serialized resource key for matching and storage.
115    pub fn resource_key(&self) -> Option<String> {
116        if self.resource_type == ResourceType::Any {
117            return Some("*".to_string());
118        }
119        let type_name = self.resource_type.name();
120        match self.resource_pattern {
121            ResourcePattern::Any => Some(format!("{}:{}", type_name, "*")),
122            ResourcePattern::Literal => Some(format!("{}:{}", type_name, self.resource_name.as_deref().unwrap_or(""))),
123            ResourcePattern::Prefixed => Some(format!(
124                "{}:{}*",
125                type_name,
126                self.resource_name.as_deref().unwrap_or("")
127            )),
128        }
129    }
130
131    /// Check whether `self` (the rule) matches the given `resource`.
132    pub fn is_match(&self, resource: &Resource) -> bool {
133        if self.resource_type == ResourceType::Any {
134            return true;
135        }
136        if self.resource_type != resource.resource_type {
137            return false;
138        }
139        match self.resource_pattern {
140            ResourcePattern::Any => true,
141            ResourcePattern::Literal => resource.resource_name.as_deref() == self.resource_name.as_deref(),
142            ResourcePattern::Prefixed => match (&resource.resource_name, &self.resource_name) {
143                (Some(target), Some(prefix)) => target.starts_with(prefix),
144                _ => false,
145            },
146        }
147    }
148
149    // getters/setters
150    pub fn resource_type(&self) -> ResourceType {
151        self.resource_type
152    }
153
154    pub fn resource_name(&self) -> Option<&str> {
155        self.resource_name.as_deref()
156    }
157
158    pub fn resource_pattern(&self) -> ResourcePattern {
159        self.resource_pattern
160    }
161
162    pub fn set_resource_type(&mut self, t: ResourceType) {
163        self.resource_type = t;
164    }
165
166    pub fn set_resource_name<S: Into<String>>(&mut self, name: Option<S>) {
167        self.resource_name = name.map(Into::into);
168    }
169
170    pub fn set_resource_pattern(&mut self, p: ResourcePattern) {
171        self.resource_pattern = p;
172    }
173}
174
175impl fmt::Display for Resource {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        match self.resource_key() {
178            Some(s) => write!(f, "{}", s),
179            None => write!(f, ""),
180        }
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_of_and_serialization() {
190        let r = Resource::of(ResourceType::Topic, Some("foo".to_string()), ResourcePattern::Literal);
191        assert_eq!(r.resource_key(), Some("Topic:foo".to_string()));
192
193        let any = Resource::of_str("*").unwrap();
194        assert_eq!(any.resource_key(), Some("*".to_string()));
195    }
196
197    #[test]
198    fn test_of_parsing_patterns() {
199        assert_eq!(
200            Resource::of_str("Topic:*").unwrap().resource_pattern,
201            ResourcePattern::Any
202        );
203        assert_eq!(
204            Resource::of_str("Topic:pref*").unwrap().resource_pattern,
205            ResourcePattern::Prefixed
206        );
207        assert_eq!(
208            Resource::of_str("Topic:foo").unwrap().resource_pattern,
209            ResourcePattern::Literal
210        );
211    }
212
213    #[test]
214    fn test_group_retry_strip() {
215        let g = format!("{}{}", mix_all::RETRY_GROUP_TOPIC_PREFIX, "mygroup");
216        let r = Resource::of_group(g);
217        assert_eq!(r.resource_name(), Some("mygroup"));
218    }
219
220    #[test]
221    fn test_is_match() {
222        let rule_any = Resource::of(ResourceType::Any, None, ResourcePattern::Any);
223        let res = Resource::of(ResourceType::Topic, Some("a".to_string()), ResourcePattern::Literal);
224        assert!(rule_any.is_match(&res));
225
226        let rule_pref = Resource::of(ResourceType::Topic, Some("pre".to_string()), ResourcePattern::Prefixed);
227        let res2 = Resource::of(
228            ResourceType::Topic,
229            Some("prefix_val".to_string()),
230            ResourcePattern::Literal,
231        );
232        assert!(rule_pref.is_match(&res2));
233
234        let rule_lit = Resource::of(ResourceType::Topic, Some("t1".to_string()), ResourcePattern::Literal);
235        assert!(rule_lit.is_match(&Resource::of(
236            ResourceType::Topic,
237            Some("t1".to_string()),
238            ResourcePattern::Literal
239        )));
240        assert!(!rule_lit.is_match(&Resource::of(
241            ResourceType::Topic,
242            Some("t2".to_string()),
243            ResourcePattern::Literal
244        )));
245    }
246
247    #[test]
248    fn test_of_list() {
249        let keys = vec!["Topic:foo".to_string(), "Topic:bar".to_string()];
250        let v = Resource::of_list(keys).unwrap();
251        assert_eq!(v.len(), 2);
252
253        let empty: Vec<String> = vec![];
254        assert!(Resource::of_list(empty).is_none());
255    }
256}