rocketmq_auth/authorization/model/
environment.rs1use std::net::IpAddr;
16use std::net::SocketAddr;
17use std::str::FromStr;
18
19use ipnetwork::IpNetwork;
20
21#[derive(Clone, Debug, Default, PartialEq, Eq)]
23pub struct Environment {
24 source_ips: Vec<String>,
25}
26
27impl Environment {
28 pub fn of(source_ip: &str) -> Option<Self> {
30 if source_ip.trim().is_empty() {
31 None
32 } else {
33 Some(Self {
34 source_ips: vec![source_ip.to_string()],
35 })
36 }
37 }
38
39 pub fn of_list<I, S>(source_ips: I) -> Option<Self>
41 where
42 I: IntoIterator<Item = S>,
43 S: Into<String>,
44 {
45 let v: Vec<String> = source_ips.into_iter().map(Into::into).collect();
46 if v.is_empty() {
47 None
48 } else {
49 Some(Self { source_ips: v })
50 }
51 }
52
53 pub fn is_match(&self, other: &Environment) -> bool {
58 if self.source_ips.is_empty() {
59 return true;
60 }
61 if other.source_ips.is_empty() {
62 return false;
63 }
64 let target = &other.source_ips[0];
65 for pattern in &self.source_ips {
66 if source_ip_matches(pattern, target) {
67 return true;
68 }
69 }
70 false
71 }
72
73 pub fn source_ips(&self) -> &Vec<String> {
74 &self.source_ips
75 }
76
77 pub fn set_source_ips<I, S>(&mut self, ips: I)
78 where
79 I: IntoIterator<Item = S>,
80 S: Into<String>,
81 {
82 self.source_ips = ips.into_iter().map(Into::into).collect();
83 }
84}
85
86pub fn source_ip_matches(pattern: &str, target: &str) -> bool {
87 let pattern = pattern.trim();
88 let target = normalize_ip_text(target);
89
90 if target.is_empty() || pattern.is_empty() {
91 return false;
92 }
93
94 if pattern == "*" {
95 return true;
96 }
97
98 if pattern.contains('*') {
99 return wildcard_match(pattern, target.as_str());
100 }
101
102 if pattern.contains('/') {
104 if let (Ok(net), Ok(ip)) = (IpNetwork::from_str(pattern), IpAddr::from_str(target.as_str())) {
105 return net.contains(ip);
106 }
107 return false;
108 }
109
110 if pattern.contains('-') {
112 let parts: Vec<&str> = pattern.splitn(2, '-').collect();
113 if parts.len() == 2 {
114 if let (Ok(start), Ok(end), Ok(ip)) = (
115 IpAddr::from_str(parts[0].trim()),
116 IpAddr::from_str(parts[1].trim()),
117 IpAddr::from_str(target.as_str()),
118 ) {
119 return ip_in_range(ip, start, end);
120 }
121 }
122 return false;
123 }
124
125 target == pattern
127}
128
129fn normalize_ip_text(value: &str) -> String {
130 let value = value.trim();
131 if let Ok(socket_addr) = SocketAddr::from_str(value) {
132 return socket_addr.ip().to_string();
133 }
134 value.to_owned()
135}
136
137fn wildcard_match(pattern: &str, target: &str) -> bool {
138 let pattern_parts: Vec<&str> = pattern.split('.').collect();
139 let target_parts: Vec<&str> = target.split('.').collect();
140 if pattern_parts.len() != target_parts.len() {
141 return false;
142 }
143
144 pattern_parts
145 .iter()
146 .zip(target_parts.iter())
147 .all(|(pattern_part, target_part)| *pattern_part == "*" || pattern_part == target_part)
148}
149
150fn ip_to_u128(ip: IpAddr) -> u128 {
151 match ip {
152 IpAddr::V4(a) => u32::from(a) as u128,
153 IpAddr::V6(b) => {
154 let octets = b.octets();
155 let mut acc = 0u128;
156 for byte in &octets {
157 acc = (acc << 8) | (*byte as u128);
158 }
159 acc
160 }
161 }
162}
163
164fn ip_in_range(ip: IpAddr, start: IpAddr, end: IpAddr) -> bool {
165 match (ip, start, end) {
166 (IpAddr::V4(a), IpAddr::V4(b), IpAddr::V4(c)) => {
167 let a = u32::from(a);
168 let b = u32::from(b);
169 let c = u32::from(c);
170 b <= a && a <= c
171 }
172 (IpAddr::V6(a), IpAddr::V6(b), IpAddr::V6(c)) => {
173 let a = ip_to_u128(IpAddr::V6(a));
174 let b = ip_to_u128(IpAddr::V6(b));
175 let c = ip_to_u128(IpAddr::V6(c));
176 b <= a && a <= c
177 }
178 _ => false,
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_of_and_list() {
188 assert!(Environment::of("").is_none());
189 assert!(Environment::of("127.0.0.1").is_some());
190 assert!(Environment::of_list(Vec::<String>::new()).is_none());
191 assert!(Environment::of_list(vec!["1.2.3.4"]).is_some());
192 }
193
194 #[test]
195 fn test_exact_match() {
196 let a = Environment::of("1.2.3.4").unwrap();
197 let b = Environment::of("1.2.3.4").unwrap();
198 assert!(a.is_match(&b));
199 let c = Environment::of("1.2.3.5").unwrap();
200 assert!(!a.is_match(&c));
201 }
202
203 #[test]
204 fn test_cidr_match() {
205 let a = Environment::of_list(vec!["192.168.0.0/16"]).unwrap();
206 let b = Environment::of("192.168.1.5").unwrap();
207 assert!(a.is_match(&b));
208 let c = Environment::of("10.0.0.1").unwrap();
209 assert!(!a.is_match(&c));
210 }
211
212 #[test]
213 fn test_range_match() {
214 let a = Environment::of_list(vec!["192.168.0.1-192.168.0.10"]).unwrap();
215 assert!(a.is_match(&Environment::of("192.168.0.5").unwrap()));
216 assert!(!a.is_match(&Environment::of("192.168.0.11").unwrap()));
217 }
218
219 #[test]
220 fn test_java_plain_acl_wildcard_match() {
221 let a = Environment::of_list(vec!["192.168.0.*", "10.10.*.*"]).unwrap();
222 assert!(a.is_match(&Environment::of("192.168.0.5").unwrap()));
223 assert!(a.is_match(&Environment::of("10.10.1.2").unwrap()));
224 assert!(!a.is_match(&Environment::of("10.11.1.2").unwrap()));
225 }
226
227 #[test]
228 fn test_source_ip_match_normalizes_socket_address() {
229 assert!(source_ip_matches("127.0.0.1", "127.0.0.1:10911"));
230 assert!(source_ip_matches("*", "127.0.0.1:10911"));
231 assert!(!source_ip_matches("127.0.0.2", "127.0.0.1:10911"));
232 }
233
234 #[test]
235 fn test_empty_behavior() {
236 let a = Environment::default();
237 let b = Environment::of("1.2.3.4").unwrap();
238 assert!(a.is_match(&b));
239 let c = Environment::default();
240 assert!(!b.is_match(&c));
241 }
242}