use base64::Engine;
use hmac::{Hmac, Mac};
use sha1::Sha1;
use crate::common::current_time_millis;
#[derive(Clone, Debug, Default)]
pub struct Credentials {
pub username: Option<String>,
pub password: Option<String>,
pub access_key: Option<String>,
pub secret_key: Option<String>,
pub endpoint: Option<String>,
pub region_id: Option<String>,
pub ram_role_name: Option<String>,
}
impl Credentials {
pub fn new() -> Self {
Self::default()
}
pub fn with_username_password(username: impl Into<String>, password: impl Into<String>) -> Self {
Self {
username: Some(username.into()),
password: Some(password.into()),
..Default::default()
}
}
pub fn with_access_key(access_key: impl Into<String>, secret_key: impl Into<String>) -> Self {
Self {
access_key: Some(access_key.into()),
secret_key: Some(secret_key.into()),
..Default::default()
}
}
pub fn with_acm(
access_key: impl Into<String>,
secret_key: impl Into<String>,
endpoint: impl Into<String>,
region_id: impl Into<String>,
) -> Self {
Self {
access_key: Some(access_key.into()),
secret_key: Some(secret_key.into()),
endpoint: Some(endpoint.into()),
region_id: Some(region_id.into()),
..Default::default()
}
}
pub fn set_endpoint(&mut self, endpoint: impl Into<String>) {
self.endpoint = Some(endpoint.into());
}
pub fn set_region_id(&mut self, region_id: impl Into<String>) {
self.region_id = Some(region_id.into());
}
pub fn set_ram_role_name(&mut self, role_name: impl Into<String>) {
self.ram_role_name = Some(role_name.into());
}
pub fn is_configured(&self) -> bool {
self.has_basic_auth() || self.has_ak_sk_auth()
}
pub fn has_basic_auth(&self) -> bool {
self.username.is_some() && self.password.is_some()
}
pub fn has_ak_sk_auth(&self) -> bool {
self.access_key.is_some() && self.secret_key.is_some()
}
pub fn has_acm_auth(&self) -> bool {
self.has_ak_sk_auth() && self.endpoint.is_some() && self.region_id.is_some()
}
pub fn generate_signature(&self, resource: &str) -> Option<SignatureInfo> {
let access_key = self.access_key.as_ref()?;
let secret_key = self.secret_key.as_ref()?;
let timestamp = current_time_millis().to_string();
let sign_str = format!("{}+{}", resource, timestamp);
let mut mac = Hmac::<Sha1>::new_from_slice(secret_key.as_bytes()).ok()?;
mac.update(sign_str.as_bytes());
let result = mac.finalize();
let signature = base64::engine::general_purpose::STANDARD.encode(result.into_bytes());
Some(SignatureInfo {
access_key: access_key.clone(),
signature,
timestamp,
})
}
pub fn generate_acm_signature(&self, resource: &str) -> Option<AcmSignatureInfo> {
let access_key = self.access_key.as_ref()?;
let secret_key = self.secret_key.as_ref()?;
let timestamp = current_time_millis().to_string();
let sign_str = format!("{}+{}", resource, timestamp);
let mut mac = Hmac::<Sha1>::new_from_slice(secret_key.as_bytes()).ok()?;
mac.update(sign_str.as_bytes());
let result = mac.finalize();
let signature = base64::engine::general_purpose::STANDARD.encode(result.into_bytes());
Some(AcmSignatureInfo {
access_key: access_key.clone(),
signature,
timestamp,
endpoint: self.endpoint.clone(),
region_id: self.region_id.clone(),
})
}
}
#[derive(Clone, Debug)]
pub struct SignatureInfo {
pub access_key: String,
pub signature: String,
pub timestamp: String,
}
#[derive(Clone, Debug)]
pub struct AcmSignatureInfo {
pub access_key: String,
pub signature: String,
pub timestamp: String,
pub endpoint: Option<String>,
pub region_id: Option<String>,
}
#[derive(Clone, Debug, Default)]
pub struct AccessToken {
pub token: String,
pub expire_time: i64,
pub global_admin: bool,
}
impl AccessToken {
pub fn is_expired(&self) -> bool {
if self.token.is_empty() {
return true;
}
current_time_millis() >= self.expire_time - 30000
}
pub fn is_valid(&self) -> bool {
!self.token.is_empty() && !self.is_expired()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_credentials_basic_auth() {
let creds = Credentials::with_username_password("admin", "password");
assert!(creds.has_basic_auth());
assert!(!creds.has_ak_sk_auth());
assert!(creds.is_configured());
}
#[test]
fn test_credentials_ak_sk_auth() {
let creds = Credentials::with_access_key("ak123", "sk456");
assert!(!creds.has_basic_auth());
assert!(creds.has_ak_sk_auth());
assert!(creds.is_configured());
}
#[test]
fn test_generate_signature() {
let creds = Credentials::with_access_key("test-ak", "test-sk");
let sig = creds.generate_signature("test-resource");
assert!(sig.is_some());
let sig = sig.unwrap();
assert_eq!(sig.access_key, "test-ak");
assert!(!sig.signature.is_empty());
assert!(!sig.timestamp.is_empty());
}
#[test]
fn test_access_token_expired() {
let token = AccessToken {
token: "test-token".to_string(),
expire_time: 0,
global_admin: false,
};
assert!(token.is_expired());
assert!(!token.is_valid());
}
#[test]
fn test_access_token_valid() {
let token = AccessToken {
token: "test-token".to_string(),
expire_time: current_time_millis() + 60000,
global_admin: false,
};
assert!(!token.is_expired());
assert!(token.is_valid());
}
}