batata_client/auth/
credentials.rs1use base64::Engine;
2use hmac::{Hmac, Mac};
3use sha1::Sha1;
4
5use crate::common::current_time_millis;
6
7#[derive(Clone, Debug, Default)]
9pub struct Credentials {
10 pub username: Option<String>,
12 pub password: Option<String>,
14 pub access_key: Option<String>,
16 pub secret_key: Option<String>,
18 pub endpoint: Option<String>,
20 pub region_id: Option<String>,
22 pub ram_role_name: Option<String>,
24}
25
26impl Credentials {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn with_username_password(username: impl Into<String>, password: impl Into<String>) -> Self {
34 Self {
35 username: Some(username.into()),
36 password: Some(password.into()),
37 ..Default::default()
38 }
39 }
40
41 pub fn with_access_key(access_key: impl Into<String>, secret_key: impl Into<String>) -> Self {
43 Self {
44 access_key: Some(access_key.into()),
45 secret_key: Some(secret_key.into()),
46 ..Default::default()
47 }
48 }
49
50 pub fn with_acm(
52 access_key: impl Into<String>,
53 secret_key: impl Into<String>,
54 endpoint: impl Into<String>,
55 region_id: impl Into<String>,
56 ) -> Self {
57 Self {
58 access_key: Some(access_key.into()),
59 secret_key: Some(secret_key.into()),
60 endpoint: Some(endpoint.into()),
61 region_id: Some(region_id.into()),
62 ..Default::default()
63 }
64 }
65
66 pub fn set_endpoint(&mut self, endpoint: impl Into<String>) {
68 self.endpoint = Some(endpoint.into());
69 }
70
71 pub fn set_region_id(&mut self, region_id: impl Into<String>) {
73 self.region_id = Some(region_id.into());
74 }
75
76 pub fn set_ram_role_name(&mut self, role_name: impl Into<String>) {
78 self.ram_role_name = Some(role_name.into());
79 }
80
81 pub fn is_configured(&self) -> bool {
83 self.has_basic_auth() || self.has_ak_sk_auth()
84 }
85
86 pub fn has_basic_auth(&self) -> bool {
88 self.username.is_some() && self.password.is_some()
89 }
90
91 pub fn has_ak_sk_auth(&self) -> bool {
93 self.access_key.is_some() && self.secret_key.is_some()
94 }
95
96 pub fn has_acm_auth(&self) -> bool {
98 self.has_ak_sk_auth() && self.endpoint.is_some() && self.region_id.is_some()
99 }
100
101 pub fn generate_signature(&self, resource: &str) -> Option<SignatureInfo> {
103 let access_key = self.access_key.as_ref()?;
104 let secret_key = self.secret_key.as_ref()?;
105
106 let timestamp = current_time_millis().to_string();
107 let sign_str = format!("{}+{}", resource, timestamp);
108
109 let mut mac = Hmac::<Sha1>::new_from_slice(secret_key.as_bytes()).ok()?;
111 mac.update(sign_str.as_bytes());
112 let result = mac.finalize();
113 let signature = base64::engine::general_purpose::STANDARD.encode(result.into_bytes());
114
115 Some(SignatureInfo {
116 access_key: access_key.clone(),
117 signature,
118 timestamp,
119 })
120 }
121
122 pub fn generate_acm_signature(&self, resource: &str) -> Option<AcmSignatureInfo> {
127 let access_key = self.access_key.as_ref()?;
128 let secret_key = self.secret_key.as_ref()?;
129
130 let timestamp = current_time_millis().to_string();
132 let sign_str = format!("{}+{}", resource, timestamp);
133
134 let mut mac = Hmac::<Sha1>::new_from_slice(secret_key.as_bytes()).ok()?;
136 mac.update(sign_str.as_bytes());
137 let result = mac.finalize();
138 let signature = base64::engine::general_purpose::STANDARD.encode(result.into_bytes());
139
140 Some(AcmSignatureInfo {
141 access_key: access_key.clone(),
142 signature,
143 timestamp,
144 endpoint: self.endpoint.clone(),
145 region_id: self.region_id.clone(),
146 })
147 }
148}
149
150#[derive(Clone, Debug)]
152pub struct SignatureInfo {
153 pub access_key: String,
154 pub signature: String,
155 pub timestamp: String,
156}
157
158#[derive(Clone, Debug)]
160pub struct AcmSignatureInfo {
161 pub access_key: String,
162 pub signature: String,
163 pub timestamp: String,
164 pub endpoint: Option<String>,
165 pub region_id: Option<String>,
166}
167
168#[derive(Clone, Debug, Default)]
170pub struct AccessToken {
171 pub token: String,
173 pub expire_time: i64,
175 pub global_admin: bool,
177}
178
179impl AccessToken {
180 pub fn is_expired(&self) -> bool {
182 if self.token.is_empty() {
183 return true;
184 }
185 current_time_millis() >= self.expire_time - 30000
187 }
188
189 pub fn is_valid(&self) -> bool {
191 !self.token.is_empty() && !self.is_expired()
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_credentials_basic_auth() {
201 let creds = Credentials::with_username_password("admin", "password");
202 assert!(creds.has_basic_auth());
203 assert!(!creds.has_ak_sk_auth());
204 assert!(creds.is_configured());
205 }
206
207 #[test]
208 fn test_credentials_ak_sk_auth() {
209 let creds = Credentials::with_access_key("ak123", "sk456");
210 assert!(!creds.has_basic_auth());
211 assert!(creds.has_ak_sk_auth());
212 assert!(creds.is_configured());
213 }
214
215 #[test]
216 fn test_generate_signature() {
217 let creds = Credentials::with_access_key("test-ak", "test-sk");
218 let sig = creds.generate_signature("test-resource");
219 assert!(sig.is_some());
220 let sig = sig.unwrap();
221 assert_eq!(sig.access_key, "test-ak");
222 assert!(!sig.signature.is_empty());
223 assert!(!sig.timestamp.is_empty());
224 }
225
226 #[test]
227 fn test_access_token_expired() {
228 let token = AccessToken {
229 token: "test-token".to_string(),
230 expire_time: 0,
231 global_admin: false,
232 };
233 assert!(token.is_expired());
234 assert!(!token.is_valid());
235 }
236
237 #[test]
238 fn test_access_token_valid() {
239 let token = AccessToken {
240 token: "test-token".to_string(),
241 expire_time: current_time_millis() + 60000,
242 global_admin: false,
243 };
244 assert!(!token.is_expired());
245 assert!(token.is_valid());
246 }
247}