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