rocketmq_auth/authorization/context/
default_authorization_context.rs1use std::collections::HashMap;
20use std::fmt;
21
22use rocketmq_security_api::Action;
23
24use crate::authentication::enums::subject_type::SubjectType;
25use crate::authorization::model::resource::Resource;
26
27pub struct SubjectWrapper {
29 subject_key: String,
30 subject_type: SubjectType,
31}
32
33impl SubjectWrapper {
34 pub fn new(subject_key: String, subject_type: SubjectType) -> Self {
35 let subject_key = normalize_subject_key(subject_key, subject_type);
36 Self {
37 subject_key,
38 subject_type,
39 }
40 }
41
42 pub fn subject_key(&self) -> &str {
43 &self.subject_key
44 }
45
46 pub fn subject_type(&self) -> SubjectType {
47 self.subject_type
48 }
49}
50
51impl fmt::Debug for SubjectWrapper {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.debug_struct("SubjectWrapper")
54 .field("subject_key", &self.subject_key)
55 .field("subject_type", &self.subject_type)
56 .finish()
57 }
58}
59
60impl Clone for SubjectWrapper {
61 fn clone(&self) -> Self {
62 Self {
63 subject_key: self.subject_key.clone(),
64 subject_type: self.subject_type,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Default)]
80pub struct DefaultAuthorizationContext {
81 subject: Option<SubjectWrapper>,
83
84 resource: Option<Resource>,
86
87 actions: Vec<Action>,
89
90 source_ip: Option<String>,
92
93 channel_id: Option<String>,
95
96 rpc_code: Option<String>,
98
99 ext_info: HashMap<String, String>,
101}
102
103impl DefaultAuthorizationContext {
104 pub fn of(
113 subject_key: impl Into<String>,
114 subject_type: SubjectType,
115 resource: Resource,
116 action: Action,
117 source_ip: impl Into<String>,
118 ) -> Self {
119 Self {
120 subject: Some(SubjectWrapper::new(subject_key.into(), subject_type)),
121 resource: Some(resource),
122 actions: vec![action],
123 source_ip: Some(source_ip.into()),
124 channel_id: None,
125 rpc_code: None,
126 ext_info: HashMap::new(),
127 }
128 }
129
130 pub fn of_multi_actions(
139 subject_key: impl Into<String>,
140 subject_type: SubjectType,
141 resource: Resource,
142 actions: Vec<Action>,
143 source_ip: impl Into<String>,
144 ) -> Self {
145 Self {
146 subject: Some(SubjectWrapper::new(subject_key.into(), subject_type)),
147 resource: Some(resource),
148 actions,
149 source_ip: Some(source_ip.into()),
150 channel_id: None,
151 rpc_code: None,
152 ext_info: HashMap::new(),
153 }
154 }
155
156 pub fn builder() -> DefaultAuthorizationContextBuilder {
158 DefaultAuthorizationContextBuilder::default()
159 }
160
161 pub fn subject_key(&self) -> Option<&str> {
163 self.subject.as_ref().map(|s| s.subject_key())
164 }
165
166 pub fn subject_type(&self) -> Option<SubjectType> {
168 self.subject.as_ref().map(|s| s.subject_type())
169 }
170
171 pub fn resource_key(&self) -> Option<String> {
173 self.resource.as_ref().and_then(|r| r.resource_key())
174 }
175
176 pub fn subject(&self) -> Option<&SubjectWrapper> {
178 self.subject.as_ref()
179 }
180
181 pub fn resource(&self) -> Option<&Resource> {
182 self.resource.as_ref()
183 }
184
185 pub fn actions(&self) -> &[Action] {
186 &self.actions
187 }
188
189 pub fn source_ip(&self) -> Option<&str> {
190 self.source_ip.as_deref()
191 }
192
193 pub fn channel_id(&self) -> Option<&str> {
194 self.channel_id.as_deref()
195 }
196
197 pub fn rpc_code(&self) -> Option<&str> {
198 self.rpc_code.as_deref()
199 }
200
201 pub fn ext_info(&self) -> &HashMap<String, String> {
202 &self.ext_info
203 }
204
205 pub fn set_subject(&mut self, subject_key: impl Into<String>, subject_type: SubjectType) {
207 self.subject = Some(SubjectWrapper::new(subject_key.into(), subject_type));
208 }
209
210 pub fn set_resource(&mut self, resource: Resource) {
211 self.resource = Some(resource);
212 }
213
214 pub fn set_actions(&mut self, actions: Vec<Action>) {
215 self.actions = actions;
216 }
217
218 pub fn set_source_ip(&mut self, source_ip: impl Into<String>) {
219 self.source_ip = Some(source_ip.into());
220 }
221
222 pub fn set_channel_id(&mut self, channel_id: impl Into<String>) {
223 self.channel_id = Some(channel_id.into());
224 }
225
226 pub fn set_rpc_code(&mut self, rpc_code: impl Into<String>) {
227 self.rpc_code = Some(rpc_code.into());
228 }
229
230 pub fn set_ext_info(&mut self, ext_info: HashMap<String, String>) {
231 self.ext_info = ext_info;
232 }
233
234 pub fn add_ext_info(&mut self, key: impl Into<String>, value: impl Into<String>) {
235 self.ext_info.insert(key.into(), value.into());
236 }
237}
238
239fn normalize_subject_key(subject_key: String, subject_type: SubjectType) -> String {
240 if let Some((_, raw_name)) = subject_key.split_once(':') {
241 return format!("{}:{}", subject_type.name(), raw_name);
242 }
243 format!("{}:{}", subject_type.name(), subject_key)
244}
245
246#[derive(Default)]
248pub struct DefaultAuthorizationContextBuilder {
249 subject: Option<SubjectWrapper>,
250 resource: Option<Resource>,
251 actions: Vec<Action>,
252 source_ip: Option<String>,
253 channel_id: Option<String>,
254 rpc_code: Option<String>,
255 ext_info: HashMap<String, String>,
256}
257
258impl DefaultAuthorizationContextBuilder {
259 pub fn subject(mut self, subject_key: impl Into<String>, subject_type: SubjectType) -> Self {
260 self.subject = Some(SubjectWrapper::new(subject_key.into(), subject_type));
261 self
262 }
263
264 pub fn resource(mut self, resource: Resource) -> Self {
265 self.resource = Some(resource);
266 self
267 }
268
269 pub fn action(mut self, action: Action) -> Self {
270 self.actions.push(action);
271 self
272 }
273
274 pub fn actions(mut self, actions: Vec<Action>) -> Self {
275 self.actions = actions;
276 self
277 }
278
279 pub fn source_ip(mut self, source_ip: impl Into<String>) -> Self {
280 self.source_ip = Some(source_ip.into());
281 self
282 }
283
284 pub fn channel_id(mut self, channel_id: impl Into<String>) -> Self {
285 self.channel_id = Some(channel_id.into());
286 self
287 }
288
289 pub fn rpc_code(mut self, rpc_code: impl Into<String>) -> Self {
290 self.rpc_code = Some(rpc_code.into());
291 self
292 }
293
294 pub fn ext_info(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
295 self.ext_info.insert(key.into(), value.into());
296 self
297 }
298
299 pub fn build(self) -> DefaultAuthorizationContext {
300 DefaultAuthorizationContext {
301 subject: self.subject,
302 resource: self.resource,
303 actions: self.actions,
304 source_ip: self.source_ip,
305 channel_id: self.channel_id,
306 rpc_code: self.rpc_code,
307 ext_info: self.ext_info,
308 }
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use rocketmq_security_api::Action;
315
316 use super::*;
317
318 #[test]
319 fn test_context_creation_with_of() {
320 let subject_key = "user:alice";
321 let subject_type = SubjectType::User;
322 let resource = Resource::of_topic("test-topic");
323 let context = DefaultAuthorizationContext::of(subject_key, subject_type, resource, Action::Pub, "192.168.1.1");
324
325 assert_eq!(context.subject_key(), Some("User:alice"));
326 assert_eq!(context.subject_type(), Some(SubjectType::User));
327 assert_eq!(context.actions().len(), 1);
328 assert_eq!(context.actions()[0], Action::Pub);
329 assert_eq!(context.source_ip(), Some("192.168.1.1"));
330 }
331
332 #[test]
333 fn test_context_creation_with_multi_actions() {
334 let subject_key = "user:bob";
335 let subject_type = SubjectType::User;
336 let resource = Resource::of_topic("test-topic");
337 let actions = vec![Action::Pub, Action::Sub];
338 let context =
339 DefaultAuthorizationContext::of_multi_actions(subject_key, subject_type, resource, actions, "10.0.0.1");
340
341 assert_eq!(context.actions().len(), 2);
342 assert!(context.actions().contains(&Action::Pub));
343 assert!(context.actions().contains(&Action::Sub));
344 }
345
346 #[test]
347 fn test_context_builder() {
348 let subject_key = "user:charlie";
349 let subject_type = SubjectType::User;
350 let resource = Resource::of_group("test-group".to_string());
351
352 let context = DefaultAuthorizationContext::builder()
353 .subject(subject_key, subject_type)
354 .resource(resource)
355 .action(Action::Create)
356 .source_ip("172.16.0.1")
357 .rpc_code("310")
358 .ext_info("region", "us-west")
359 .build();
360
361 assert_eq!(context.subject_key(), Some("User:charlie"));
362 assert_eq!(context.rpc_code(), Some("310"));
363 assert_eq!(context.ext_info().get("region"), Some(&"us-west".to_string()));
364 }
365
366 #[test]
367 fn test_context_setters() {
368 let mut context = DefaultAuthorizationContext::default();
369
370 context.set_subject("user:dave", SubjectType::User);
371 context.set_resource(Resource::of_topic("new-topic"));
372 context.set_actions(vec![Action::Delete]);
373 context.set_source_ip("203.0.113.1");
374 context.set_rpc_code("500");
375 context.add_ext_info("environment", "production");
376
377 assert_eq!(context.subject_key(), Some("User:dave"));
378 assert_eq!(context.actions().len(), 1);
379 assert_eq!(context.source_ip(), Some("203.0.113.1"));
380 assert_eq!(context.rpc_code(), Some("500"));
381 assert_eq!(context.ext_info().get("environment"), Some(&"production".to_string()));
382 }
383
384 #[test]
385 fn test_resource_key_generation() {
386 let subject_key = "user:eve";
387 let subject_type = SubjectType::User;
388 let resource = Resource::of_topic("my-topic");
389 let context = DefaultAuthorizationContext::of(subject_key, subject_type, resource, Action::Get, "10.10.10.10");
390
391 let resource_key = context.resource_key();
392 assert!(resource_key.is_some());
393 let key = resource_key.unwrap();
394 assert!(key.contains("Topic"));
395 assert!(key.contains("my-topic"));
396 }
397
398 #[test]
399 fn test_subject_wrapper() {
400 let wrapper = SubjectWrapper::new("user:test".to_string(), SubjectType::User);
401 assert_eq!(wrapper.subject_key(), "User:test");
402 assert_eq!(wrapper.subject_type(), SubjectType::User);
403
404 let cloned = wrapper.clone();
406 assert_eq!(cloned.subject_key(), wrapper.subject_key());
407 assert_eq!(cloned.subject_type(), wrapper.subject_type());
408
409 let debug_str = format!("{:?}", wrapper);
411 assert!(debug_str.contains("User:test"));
412 }
413}