huaweicloud_sdk_rust_obs/
auth.rs1use crate::{client::Client, config::SignatureType, error::ObsError};
2use ::base64::{engine::general_purpose, Engine};
3use chrono::{TimeZone, Utc};
4use hmacsha1::hmac_sha1;
5use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
6use std::{collections::HashMap, str::FromStr};
7
8const RFC1123: &str = "%a, %d %b %Y %H:%M:%S GMT";
9
10
11pub trait Authorization {
12 fn signature(
13 &self,
14 method: &str,
15 params: HashMap<String, String>,
16 headers: HashMap<String, Vec<String>>,
17 canonicalized_url: String,
18 ) -> Result<String, ObsError>;
19
20 fn auth(
21 &self,
22 method: &str,
23 bucket: &str,
24 params: HashMap<String, String>,
25 headers: HashMap<String, Vec<String>>,
26 canonicalized_url: String,
27 ) -> Result<HeaderMap, ObsError>;
28}
29
30impl Authorization for Client {
31 fn signature(
32 &self,
33 method: &str,
34 _params: HashMap<String, String>,
35 headers: HashMap<String, Vec<String>>,
36 canonicalized_url: String,
37 ) -> Result<String, ObsError> {
38 let attach_headers = attach_headers(headers, true);
39
40 let string_to_sign = vec![
41 method, "\n",
43 &attach_headers, "\n",
45 &canonicalized_url,
46 ]
47 .join("");
48
49 let security = self.security();
52 match security {
53 Some(s) => {
54 let signature = signature(&string_to_sign, s.sk())?;
55 Ok(signature)
56 }
57 None => Err(ObsError::Security),
58 }
59 }
60
61 fn auth(
62 &self,
63 method: &str,
64 bucket: &str,
65 params: HashMap<String, String>,
66 mut headers: HashMap<String, Vec<String>>,
67 canonicalized_url: String,
68 ) -> Result<HeaderMap, ObsError> {
69 let is_v4 = matches!(self.config().signature_type, SignatureType::V4);
70 if !bucket.is_empty() {
71 headers.insert(
72 "Host".into(),
73 vec![format!("{}.{}", bucket, self.config().endpoint())],
74 );
75 } else {
76 headers.insert("Host".into(), vec![self.config().endpoint().to_string()]);
77 }
78
79 prepare_host_and_date(&mut headers, self.config().endpoint(), is_v4);
80
81 let sign = self.signature(method, params, headers.clone(), canonicalized_url)?;
82
83 let security = self.security();
84 match security {
85 Some(s) => {
86 let value = format!("OBS {}:{}", s.ak(), sign);
87 let mut h = HeaderMap::new();
88 h.insert(
89 "Authorization",
90 HeaderValue::from_str(value.as_str()).unwrap(),
91 );
92 for (key, value) in headers.iter() {
93 h.insert(
94 HeaderName::from_str(key).unwrap(),
95 HeaderValue::from_str(&value.join(",")).unwrap(),
96 );
97 }
98 Ok(h)
99 }
100 None => Err(ObsError::Security),
101 }
102 }
103}
104
105fn prepare_host_and_date(
106 headers: &mut HashMap<String, Vec<String>>,
107 _host_name: &str,
108 is_v4: bool,
109) {
110 if let Some(date) = headers.get("x-amz-date") {
111 let mut flag = false;
112 if date.len() == 1 {
113 if is_v4 {
114 if let Ok(t) = Utc.datetime_from_str(&date[0], "%Y%m%dT%H%M%SZ") {
116 headers.insert("Date".into(), vec![t.format(RFC1123).to_string()]);
117 flag = true;
118 }
119 } else if date[0].ends_with("GMT") {
120 headers.insert("Date".into(), vec![date[0].clone()]);
121 flag = true;
122 }
123 }
124 if !flag {
125 headers.remove("x-amz-date");
126 }
127 }
128 if !headers.contains_key("Date") {
129 headers.insert("Date".into(), vec![Utc::now().format(RFC1123).to_string()]);
130 }
131}
132
133fn string_to_sign(
171 keys: Vec<String>,
172 is_obs: bool,
173 headers: HashMap<String, Vec<String>>,
174) -> Vec<String> {
175 let mut sign = Vec::with_capacity(keys.len());
176
177 for key in keys {
178 let prefix_header = if is_obs { "x-obs-" } else { "x-amz-" };
179 let prefix_meta_header = if is_obs { "x-obs-meta-" } else { "x-amz-meta-" };
180 let mut value = String::new();
181 if key.starts_with(prefix_header) {
182 if key.starts_with(prefix_meta_header) {
183 let header_value = headers.get(&key).unwrap();
184 for (index, val) in header_value.iter().enumerate() {
185 value.push_str(val.trim());
186 if index != header_value.len() - 1 {
187 value.push(',');
188 }
189 }
190 } else {
191 value = headers.get(&key).unwrap().join(",")
192 }
193 value = format!("{}:{}", key, &value);
194 } else {
195 value = headers.get(&key).unwrap().join(",");
196 }
197 sign.push(value);
198 }
199 sign
200}
201
202fn attach_headers(headers: HashMap<String, Vec<String>>, is_obs: bool) -> String {
203 let mut _headers = HashMap::with_capacity(headers.len());
204 let mut keys = vec![];
205 let headers: HashMap<String, Vec<String>> = headers
206 .into_iter()
207 .filter(|(key, _)| !key.trim().to_lowercase().is_empty())
208 .collect::<_>();
209 for (key, value) in headers {
210 let _key = key.trim().to_lowercase();
211 let prefix_header = if is_obs { "x-obs-" } else { "x-amz-" };
212 if _key == "content-md5"
213 || _key == "content-type"
214 || _key == "date"
215 || _key.starts_with(prefix_header)
216 {
217 keys.push(_key.clone());
218 _headers.insert(_key, value);
219 }
220 }
221
222 for interested_header in ["content-md5", "content-type", "date"] {
223 if !_headers.contains_key(interested_header) {
224 _headers.insert(interested_header.into(), vec![]);
225 keys.push(interested_header.into());
226 }
227 }
228
229 let date_camel_header = if is_obs { "X-obs-Date" } else { "X-Amz-Date" };
230 let data_header = date_camel_header.to_lowercase();
231
232 if _headers.contains_key(&data_header) || _headers.contains_key(date_camel_header) {
233 _headers.insert(date_camel_header.into(), vec![rfc_1123()]);
234 }
235
236 keys.sort();
237
238 let to_sign = string_to_sign(keys, is_obs, _headers);
239 to_sign.join("\n")
240}
241
242fn signature(string_to_sign: &str, sk: &str) -> Result<String, ObsError> {
245 let hash = hmac_sha1(sk.as_bytes(), string_to_sign.as_bytes());
246 let hs = general_purpose::STANDARD.encode(hash);
247 Ok(hs)
248}
249
250fn rfc_1123() -> String {
251 let date = Utc::now().format(RFC1123).to_string();
252 date
253}