wae_storage/providers/
azure_blob.rs1use crate::{StorageConfig, StorageProvider, StorageResult};
4
5use base64::{Engine as _, engine::general_purpose::STANDARD};
6use chrono::{Duration, Utc};
7use hmac::{Hmac, Mac};
8use sha2::Sha256;
9use url::Url;
10use wae_types::WaeError;
11
12pub struct AzureBlobProvider;
16
17impl AzureBlobProvider {
18 fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
20 let mut mac = Hmac::<Sha256>::new_from_slice(key).expect("HMAC can take key of any size");
21 mac.update(data);
22 mac.finalize().into_bytes().to_vec()
23 }
24}
25
26impl StorageProvider for AzureBlobProvider {
27 fn sign_url(&self, path: &str, config: &StorageConfig) -> StorageResult<Url> {
28 if path.is_empty() {
29 return Err(WaeError::invalid_params("path", "Empty path"));
30 }
31
32 if path.starts_with("http://") || path.starts_with("https://") {
33 return Url::parse(path).map_err(|e| WaeError::invalid_params("path", e.to_string()));
34 }
35
36 let clean_path = path.trim_start_matches('/');
37 let now = Utc::now();
38 let _expiration = (now + Duration::seconds(3600)).timestamp();
39 let x_ms_version = "2023-11-03";
40 let _x_ms_date = now.format("%a, %d %b %Y %H:%M:%S GMT").to_string();
41
42 let host_str = config.cdn_url.clone().unwrap_or_else(|| {
43 if let Some(endpoint) = &config.endpoint {
44 endpoint.clone()
45 }
46 else {
47 format!("https://{}.blob.core.windows.net/{}", config.secret_id, config.bucket)
48 }
49 });
50
51 let host_url =
52 if host_str.starts_with("http") { Url::parse(&host_str) } else { Url::parse(&format!("https://{}", host_str)) }
53 .map_err(|e| WaeError::invalid_params("host", e.to_string()))?;
54
55 let mut url = host_url.join(clean_path).map_err(|e| WaeError::invalid_params("path", e.to_string()))?;
56
57 let _method = "r";
58 let start = "";
59 let now = Utc::now();
60 let expiry = (now + Duration::seconds(3600)).format("%Y-%m-%dT%H:%M:%SZ").to_string();
61 let resource = "b";
62 let permissions = "r";
63 let version = x_ms_version;
64
65 let account_key = STANDARD
66 .decode(&config.secret_key)
67 .map_err(|e| WaeError::invalid_params("secret_key", format!("Invalid base64: {}", e)))?;
68
69 let string_to_sign = format!(
70 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
71 permissions,
72 start,
73 expiry,
74 format!("/{}/{}/{}", config.secret_id, config.bucket, clean_path),
75 "",
76 "",
77 "",
78 "",
79 "",
80 "",
81 "",
82 version,
83 "",
84 ""
85 );
86
87 let signature = Self::hmac_sha256(&account_key, string_to_sign.as_bytes());
88 let signature_base64 = STANDARD.encode(signature);
89
90 url.query_pairs_mut()
91 .append_pair("sv", version)
92 .append_pair("sr", resource)
93 .append_pair("sp", permissions)
94 .append_pair("se", &expiry)
95 .append_pair("sig", &signature_base64);
96
97 Ok(url)
98 }
99
100 fn get_presigned_put_url(&self, key: &str, config: &StorageConfig) -> StorageResult<Url> {
101 let clean_key = key.trim_start_matches('/');
102 let now = Utc::now();
103 let _expiration = (now + Duration::seconds(900)).timestamp();
104 let x_ms_version = "2023-11-03";
105
106 let host_str = config
107 .endpoint
108 .clone()
109 .unwrap_or_else(|| format!("https://{}.blob.core.windows.net/{}", config.secret_id, config.bucket));
110
111 let host_url =
112 if host_str.starts_with("http") { Url::parse(&host_str) } else { Url::parse(&format!("https://{}", host_str)) }
113 .map_err(|e| WaeError::invalid_params("host", e.to_string()))?;
114
115 let mut url = host_url.join(clean_key).map_err(|e| WaeError::invalid_params("key", e.to_string()))?;
116
117 let permissions = "w";
118 let resource = "b";
119 let version = x_ms_version;
120
121 let account_key = STANDARD
122 .decode(&config.secret_key)
123 .map_err(|e| WaeError::invalid_params("secret_key", format!("Invalid base64: {}", e)))?;
124
125 let string_to_sign = format!(
126 "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
127 permissions,
128 "",
129 &format!("{}", (Utc::now() + Duration::seconds(900)).format("%Y-%m-%dT%H:%M:%SZ")),
130 format!("/{}/{}/{}", config.secret_id, config.bucket, clean_key),
131 "",
132 "",
133 "",
134 "",
135 "",
136 "",
137 "",
138 version,
139 "",
140 ""
141 );
142
143 let signature = Self::hmac_sha256(&account_key, string_to_sign.as_bytes());
144 let signature_base64 = STANDARD.encode(signature);
145
146 url.query_pairs_mut()
147 .append_pair("sv", version)
148 .append_pair("sr", resource)
149 .append_pair("sp", permissions)
150 .append_pair("se", &format!("{}", (Utc::now() + Duration::seconds(900)).format("%Y-%m-%dT%H:%M:%SZ")))
151 .append_pair("sig", &signature_base64);
152
153 Ok(url)
154 }
155}