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