huaweicloud_sdk_rust_obs/
auth.rs

1use 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, //HTTP-Verb
43            "\n",
44            &attach_headers, // Content-MD5 \n Content-Type \n
45            "\n",
46            &canonicalized_url,
47        ]
48        .join("");
49
50        // println!("string_to_sign: {:?}", &string_to_sign);
51
52        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                // 20060102T150405Z
116                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
134// fn encode_headers(headers: HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
135//     headers
136//         .into_iter()
137//         .map(|(key, values)| {
138//             (
139//                 key,
140//                 values
141//                     .iter()
142//                     .map(|v| urlencoding::encode(v).to_string())
143//                     .collect::<Vec<String>>(),
144//             )
145//         })
146//         .collect::<HashMap<String, Vec<String>>>()
147// }
148
149// fn signature_header(
150//     headers: HashMap<String, Vec<String>>,
151// ) -> (Vec<String>, HashMap<String, Vec<String>>) {
152//     let mut signed_headers = vec![];
153//     let mut headers2 = HashMap::with_capacity(headers.len());
154//     for (key, value) in headers {
155//         let key2 = key.trim().to_lowercase();
156//         if !key2.is_empty() {
157//             signed_headers.push(key2.clone());
158//             headers2.insert(key2, value);
159//         }
160//     }
161//     signed_headers.sort();
162//     (signed_headers, headers2)
163// }
164
165// fn credential(ak: &str, region: &str, short_date: &str) -> (String, String) {
166//     let scope = format!("{}/{}/{}/{}", short_date, region, "s3", "aws4_request");
167//     // return fmt.Sprintf("%s/%s", ak, scope), scope
168//     (format!("{}/{}", ak, &scope), scope)
169// }
170
171fn 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
243/// 签名,算法如下:
244/// > Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) )
245fn 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}