rocketmq_client_rust/common/
session_credentials.rs1use std::collections::HashMap;
16use std::fmt;
17use std::fs;
18use std::path::PathBuf;
19
20use cheetah_string::CheetahString;
21
22pub const ACCESS_KEY: &str = "AccessKey";
23pub const SECRET_KEY: &str = "SecretKey";
24pub const SIGNATURE: &str = "Signature";
25pub const SECURITY_TOKEN: &str = "SecurityToken";
26
27fn get_key_file_path() -> PathBuf {
28 if let Ok(key_file) = std::env::var("rocketmq.client.keyFile") {
29 PathBuf::from(key_file)
30 } else if let Ok(home) = std::env::var("HOME").or_else(|_| std::env::var("USERPROFILE")) {
31 PathBuf::from(home).join("key")
32 } else {
33 PathBuf::from("key")
34 }
35}
36
37#[derive(Clone, Default)]
38pub struct SessionCredentials {
39 access_key: Option<CheetahString>,
40 secret_key: Option<CheetahString>,
41 security_token: Option<CheetahString>,
42 signature: Option<CheetahString>,
43}
44
45impl SessionCredentials {
46 pub fn new() -> Self {
47 let mut credentials = Self::default();
48 credentials.load_from_file();
49 credentials
50 }
51
52 pub fn with_keys(access_key: impl Into<CheetahString>, secret_key: impl Into<CheetahString>) -> Self {
53 Self {
54 access_key: Some(access_key.into()),
55 secret_key: Some(secret_key.into()),
56 security_token: None,
57 signature: None,
58 }
59 }
60
61 pub fn with_token(
62 access_key: impl Into<CheetahString>,
63 secret_key: impl Into<CheetahString>,
64 security_token: impl Into<CheetahString>,
65 ) -> Self {
66 Self {
67 access_key: Some(access_key.into()),
68 secret_key: Some(secret_key.into()),
69 security_token: Some(security_token.into()),
70 signature: None,
71 }
72 }
73
74 fn load_from_file(&mut self) {
75 let key_file = get_key_file_path();
76 if let Ok(content) = fs::read_to_string(key_file) {
77 if let Ok(properties) = self.parse_properties(&content) {
78 self.update_content(&properties);
79 }
80 }
81 }
82
83 fn parse_properties(&self, content: &str) -> Result<HashMap<String, String>, ()> {
84 let mut properties = HashMap::new();
85 for line in content.lines() {
86 let line = line.trim();
87 if line.is_empty() || line.starts_with('#') {
88 continue;
89 }
90 if let Some(pos) = line.find('=') {
91 let key = line[..pos].trim().to_string();
92 let value = line[pos + 1..].trim().to_string();
93 properties.insert(key, value);
94 }
95 }
96 Ok(properties)
97 }
98
99 pub fn update_content(&mut self, properties: &HashMap<String, String>) {
100 if let Some(value) = properties.get(ACCESS_KEY) {
101 self.access_key = Some(CheetahString::from_slice(value.trim()));
102 }
103 if let Some(value) = properties.get(SECRET_KEY) {
104 self.secret_key = Some(CheetahString::from_slice(value.trim()));
105 }
106 if let Some(value) = properties.get(SECURITY_TOKEN) {
107 self.security_token = Some(CheetahString::from_slice(value.trim()));
108 }
109 }
110
111 pub fn access_key(&self) -> Option<&CheetahString> {
112 self.access_key.as_ref()
113 }
114
115 pub fn set_access_key(&mut self, access_key: impl Into<CheetahString>) {
116 self.access_key = Some(access_key.into());
117 }
118
119 pub fn secret_key(&self) -> Option<&CheetahString> {
120 self.secret_key.as_ref()
121 }
122
123 pub fn set_secret_key(&mut self, secret_key: impl Into<CheetahString>) {
124 self.secret_key = Some(secret_key.into());
125 }
126
127 pub fn signature(&self) -> Option<&CheetahString> {
128 self.signature.as_ref()
129 }
130
131 pub fn set_signature(&mut self, signature: impl Into<CheetahString>) {
132 self.signature = Some(signature.into());
133 }
134
135 pub fn security_token(&self) -> Option<&CheetahString> {
136 self.security_token.as_ref()
137 }
138
139 pub fn set_security_token(&mut self, security_token: impl Into<CheetahString>) {
140 self.security_token = Some(security_token.into());
141 }
142}
143
144impl PartialEq for SessionCredentials {
145 fn eq(&self, other: &Self) -> bool {
146 self.access_key == other.access_key && self.secret_key == other.secret_key && self.signature == other.signature
147 }
148}
149
150impl Eq for SessionCredentials {}
151
152fn redacted_if_present<T>(value: &Option<T>) -> &'static str {
153 if value.is_some() {
154 "<redacted>"
155 } else {
156 "<unset>"
157 }
158}
159
160impl fmt::Debug for SessionCredentials {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.debug_struct("SessionCredentials")
163 .field("access_key", &redacted_if_present(&self.access_key))
164 .field("secret_key", &redacted_if_present(&self.secret_key))
165 .field("signature", &redacted_if_present(&self.signature))
166 .field("security_token", &redacted_if_present(&self.security_token))
167 .finish()
168 }
169}
170
171impl std::hash::Hash for SessionCredentials {
172 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
173 self.access_key.hash(state);
174 self.secret_key.hash(state);
175 self.signature.hash(state);
176 }
177}
178
179impl fmt::Display for SessionCredentials {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(
182 f,
183 "SessionCredentials [accessKey={}, secretKey={}, signature={}, securityToken={}]",
184 redacted_if_present(&self.access_key),
185 redacted_if_present(&self.secret_key),
186 redacted_if_present(&self.signature),
187 redacted_if_present(&self.security_token)
188 )
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn session_credentials_new_and_default() {
198 let credentials = SessionCredentials::new();
199 assert!(credentials.access_key().is_none());
200 let credentials = SessionCredentials::default();
201 assert!(credentials.secret_key().is_none());
202 }
203
204 #[test]
205 fn session_credentials_with_keys() {
206 let access_key = "ak";
207 let secret_key = "sk";
208 let credentials = SessionCredentials::with_keys(access_key, secret_key);
209 assert_eq!(credentials.access_key().unwrap(), access_key);
210 assert_eq!(credentials.secret_key().unwrap(), secret_key);
211 assert!(credentials.security_token().is_none());
212 assert!(credentials.signature().is_none());
213 }
214
215 #[test]
216 fn session_credentials_with_token() {
217 let access_key = "ak";
218 let secret_key = "sk";
219 let token = "token";
220 let credentials = SessionCredentials::with_token(access_key, secret_key, token);
221 assert_eq!(credentials.access_key().unwrap(), access_key);
222 assert_eq!(credentials.secret_key().unwrap(), secret_key);
223 assert_eq!(credentials.security_token().unwrap(), token);
224 assert!(credentials.signature().is_none());
225 }
226
227 #[test]
228 fn session_credentials_parse_properties() {
229 let content = "AccessKey=ak123\nSecretKey=sk456\n#comment\n \nSecurityToken=tk789\nSignature=sig123";
230 let credentials = SessionCredentials::default();
231 let properties = credentials.parse_properties(content).unwrap();
232 assert_eq!(properties.get("AccessKey").unwrap(), "ak123");
233 assert_eq!(properties.get("SecretKey").unwrap(), "sk456");
234 assert_eq!(properties.get("SecurityToken").unwrap(), "tk789");
235 assert_eq!(properties.get("Signature").unwrap(), "sig123");
236 }
237
238 #[test]
239 fn session_credentials_update_content() {
240 let mut properties = HashMap::new();
241 properties.insert(ACCESS_KEY.to_string(), "ak".to_string());
242 properties.insert(SECRET_KEY.to_string(), "sk".to_string());
243 properties.insert(SECURITY_TOKEN.to_string(), "token".to_string());
244
245 let mut credentials = SessionCredentials::default();
246 credentials.update_content(&properties);
247
248 assert_eq!(credentials.access_key().unwrap(), "ak");
249 assert_eq!(credentials.secret_key().unwrap(), "sk");
250 assert_eq!(credentials.security_token().unwrap(), "token");
251 }
252
253 #[test]
254 fn session_credentials_getters_and_setters() {
255 let mut credentials = SessionCredentials::default();
256 credentials.set_access_key("ak");
257 credentials.set_secret_key("sk");
258 credentials.set_security_token("token");
259 credentials.set_signature("sig");
260
261 assert_eq!(credentials.access_key().unwrap(), "ak");
262 assert_eq!(credentials.secret_key().unwrap(), "sk");
263 assert_eq!(credentials.security_token().unwrap(), "token");
264 assert_eq!(credentials.signature().unwrap(), "sig");
265 }
266
267 #[test]
268 fn session_credentials_eq_and_hash() {
269 let c1 = SessionCredentials::with_keys("ak", "sk");
270 let c2 = SessionCredentials::with_keys("ak", "sk");
271 let c3 = SessionCredentials::with_keys("ak2", "sk");
272
273 assert_eq!(c1, c2);
274 assert_ne!(c1, c3);
275
276 use std::collections::hash_map::DefaultHasher;
277 use std::hash::Hash;
278 use std::hash::Hasher;
279 let mut h1 = DefaultHasher::new();
280 c1.hash(&mut h1);
281 let mut h2 = DefaultHasher::new();
282 c2.hash(&mut h2);
283 assert_eq!(h1.finish(), h2.finish());
284 }
285
286 #[test]
287 fn session_credentials_display() {
288 let mut credentials = SessionCredentials::with_token("ak", "sk", "tk");
289 credentials.set_signature("sig");
290
291 let display = format!("{}", credentials);
292 let expected = "SessionCredentials [accessKey=<redacted>, secretKey=<redacted>, signature=<redacted>, \
293 securityToken=<redacted>]";
294
295 assert_eq!(display, expected);
296 for secret in ["ak", "sk", "tk", "sig"] {
297 assert!(!display.contains(&format!("\"{secret}\"")));
298 }
299 }
300
301 #[test]
302 fn session_credentials_debug_redacts_sensitive_fields() {
303 let mut credentials = SessionCredentials::with_token("ak", "sk", "tk");
304 credentials.set_signature("sig");
305
306 let debug = format!("{credentials:?}");
307
308 assert!(debug.contains("access_key: \"<redacted>\""));
309 assert!(debug.contains("secret_key: \"<redacted>\""));
310 assert!(debug.contains("signature: \"<redacted>\""));
311 assert!(debug.contains("security_token: \"<redacted>\""));
312 for secret in ["ak", "sk", "tk", "sig"] {
313 assert!(!debug.contains(&format!("\"{secret}\"")));
314 }
315 }
316}