huaweicloud_sdk_rust_obs/
auth.rs

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