qiniu_http_client/client/
authorization.rs

1use anyhow::Error as AnyError;
2use auto_impl::auto_impl;
3use chrono::Utc;
4use dyn_clonable::clonable;
5use qiniu_credential::{Credential, CredentialProvider, GetOptions, Uri};
6use qiniu_http::{
7    header::{AUTHORIZATION, CONTENT_TYPE},
8    HeaderValue, RequestParts, SyncRequest,
9};
10use qiniu_upload_token::{ToStringError, UploadTokenProvider};
11use std::{
12    env::{remove_var, set_var, var_os},
13    fmt::Debug,
14    io::Error as IoError,
15    mem::take,
16    time::Duration,
17};
18use tap::Tap;
19use thiserror::Error;
20use url::ParseError as UrlParseError;
21
22#[cfg(feature = "async")]
23use {futures::future::BoxFuture, qiniu_http::AsyncRequest};
24
25/// 七牛鉴权签名接口
26///
27/// 对 HTTP 请求进行签名
28#[clonable]
29#[auto_impl(&, &mut, Box, Rc, Arc)]
30pub trait AuthorizationProvider: Clone + Debug + Sync + Send {
31    /// 使用指定的鉴权方式对 HTTP 请求进行签名
32    ///
33    /// 该方法的异步版本为 [`Self::async_sign`]。
34    fn sign(&self, request: &mut SyncRequest) -> AuthorizationResult<()>;
35
36    /// 使用指定的鉴权方式对异步 HTTP 请求进行签名
37    #[cfg(feature = "async")]
38    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
39    fn async_sign<'a>(&'a self, request: &'a mut AsyncRequest<'_>) -> BoxFuture<'a, AuthorizationResult<()>>;
40}
41
42/// 上传凭证鉴权签名
43#[derive(Clone, Debug)]
44pub struct UploadTokenAuthorization<P: ?Sized>(P);
45
46impl<P> UploadTokenAuthorization<P> {
47    /// 创建上传凭证鉴权签名
48    #[inline]
49    pub fn new(provider: P) -> Self {
50        Self(provider)
51    }
52}
53
54impl<P> From<P> for UploadTokenAuthorization<P> {
55    #[inline]
56    fn from(provider: P) -> Self {
57        Self::new(provider)
58    }
59}
60
61impl<P: UploadTokenProvider + Clone> AuthorizationProvider for UploadTokenAuthorization<P> {
62    fn sign(&self, request: &mut SyncRequest) -> AuthorizationResult<()> {
63        let authorization = uptoken_authorization(&self.0.to_token_string(Default::default())?);
64        set_authorization(request, HeaderValue::from_str(&authorization).unwrap());
65        Ok(())
66    }
67
68    #[cfg(feature = "async")]
69    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
70    fn async_sign<'a>(&'a self, request: &'a mut AsyncRequest<'_>) -> BoxFuture<'a, AuthorizationResult<()>> {
71        Box::pin(async move {
72            let authorization = uptoken_authorization(&self.0.async_to_token_string(Default::default()).await?);
73            set_authorization(request, HeaderValue::from_str(&authorization).unwrap());
74            Ok(())
75        })
76    }
77}
78
79/// 七牛签名算法 V1 鉴权签名
80#[derive(Clone, Debug)]
81pub struct CredentialAuthorizationV1<P: ?Sized>(P);
82
83impl<P> CredentialAuthorizationV1<P> {
84    /// 创建七牛签名算法 V1 鉴权签名
85    #[inline]
86    pub fn new(provider: P) -> Self {
87        Self(provider)
88    }
89}
90
91impl<P> From<P> for CredentialAuthorizationV1<P> {
92    #[inline]
93    fn from(provider: P) -> Self {
94        Self::new(provider)
95    }
96}
97
98impl<P: CredentialProvider + Clone> AuthorizationProvider for CredentialAuthorizationV1<P> {
99    fn sign(&self, request: &mut SyncRequest) -> AuthorizationResult<()> {
100        _sign(&self.0, request, Default::default())?;
101        return Ok(());
102
103        fn _sign(
104            credential_provider: impl CredentialProvider + Clone,
105            request: &mut SyncRequest,
106            get_options: GetOptions,
107        ) -> AuthorizationResult<()> {
108            let authorization =
109                authorization_v1_for_request(credential_provider.get(get_options)?.credential(), request)?;
110            set_authorization(request, HeaderValue::from_str(&authorization).unwrap());
111            Ok(())
112        }
113    }
114
115    #[cfg(feature = "async")]
116    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
117    fn async_sign<'a>(&'a self, request: &'a mut AsyncRequest<'_>) -> BoxFuture<'a, AuthorizationResult<()>> {
118        return Box::pin(async move {
119            _sign(&self.0, request, Default::default()).await?;
120            Ok(())
121        });
122
123        async fn _sign(
124            credential_provider: impl CredentialProvider + Clone,
125            request: &mut AsyncRequest<'_>,
126            get_options: GetOptions,
127        ) -> AuthorizationResult<()> {
128            let authorization = authorization_v1_for_async_request(
129                credential_provider.async_get(get_options).await?.credential(),
130                request,
131            )
132            .await?;
133            set_authorization(request, HeaderValue::from_str(&authorization).unwrap());
134            Ok(())
135        }
136    }
137}
138
139fn authorization_v1_for_request(credential: &Credential, request: &mut SyncRequest) -> AuthorizationResult<String> {
140    let (parts, mut body) = take(request).into_parts_and_body();
141    credential
142        .authorization_v1_for_request_with_body_reader(parts.url(), parts.headers().get(CONTENT_TYPE), &mut body)
143        .tap(|_| {
144            *request = SyncRequest::from_parts_and_body(parts, body);
145        })
146        .map_err(|err| err.into())
147}
148
149#[cfg(feature = "async")]
150async fn authorization_v1_for_async_request(
151    credential: &Credential,
152    request: &mut AsyncRequest<'_>,
153) -> AuthorizationResult<String> {
154    let (parts, mut body) = take(request).into_parts_and_body();
155    credential
156        .authorization_v1_for_request_with_async_body_reader(parts.url(), parts.headers().get(CONTENT_TYPE), &mut body)
157        .await
158        .tap(|_| {
159            *request = AsyncRequest::from_parts_and_body(parts, body);
160        })
161        .map_err(|err| err.into())
162}
163
164/// 全局禁用时间戳签名
165pub fn global_disable_timestamp_signature() {
166    set_var(DISABLE_QINIU_TIMESTAMP_SIGNATURE, "1");
167}
168
169/// 全局启用时间戳签名
170pub fn global_enable_timestamp_signature() {
171    remove_var(DISABLE_QINIU_TIMESTAMP_SIGNATURE);
172}
173
174/// 七牛签名算法 V2 鉴权签名
175#[derive(Clone, Debug)]
176pub struct CredentialAuthorizationV2<P: ?Sized> {
177    timestamp_signature_enabled: bool,
178    provider: P,
179}
180
181const DISABLE_QINIU_TIMESTAMP_SIGNATURE: &str = "DISABLE_QINIU_TIMESTAMP_SIGNATURE";
182
183impl<P> CredentialAuthorizationV2<P> {
184    /// 创建七牛签名算法 V2 鉴权签名
185    ///
186    /// 可以通过 `DISABLE_QINIU_TIMESTAMP_SIGNATURE` 环境变量禁用时间戳签名,或是调用 [`Self::disable_timestamp_signature`] 来禁用时间戳签名
187    #[inline]
188    pub fn new(provider: P) -> Self {
189        Self {
190            provider,
191            timestamp_signature_enabled: var_os(DISABLE_QINIU_TIMESTAMP_SIGNATURE).is_none(),
192        }
193    }
194
195    /// 禁用时间戳签名
196    ///
197    /// 该方法将覆盖 `DISABLE_QINIU_TIMESTAMP_SIGNATURE` 环境变量的设置
198    #[inline]
199    pub fn disable_timestamp_signature(&mut self) -> &mut Self {
200        self.timestamp_signature_enabled = false;
201        self
202    }
203
204    /// 启用时间戳签名
205    ///
206    /// 该方法将覆盖 `DISABLE_QINIU_TIMESTAMP_SIGNATURE` 环境变量的设置
207    #[inline]
208    pub fn enable_timestamp_signature(&mut self) -> &mut Self {
209        self.timestamp_signature_enabled = true;
210        self
211    }
212}
213
214impl<P> From<P> for CredentialAuthorizationV2<P> {
215    #[inline]
216    fn from(provider: P) -> Self {
217        Self::new(provider)
218    }
219}
220
221impl<P: CredentialProvider + Clone> AuthorizationProvider for CredentialAuthorizationV2<P> {
222    fn sign(&self, request: &mut SyncRequest) -> AuthorizationResult<()> {
223        _sign(
224            &self.provider,
225            self.timestamp_signature_enabled,
226            request,
227            Default::default(),
228        )?;
229        return Ok(());
230
231        fn _sign(
232            credential_provider: impl CredentialProvider + Clone,
233            timestamp_signature_enabled: bool,
234            request: &mut SyncRequest,
235            get_options: GetOptions,
236        ) -> AuthorizationResult<()> {
237            if timestamp_signature_enabled {
238                request.headers_mut().insert(X_QINIU_DATE, make_x_qiniu_date_value());
239            }
240            let authorization =
241                authorization_v2_for_request(credential_provider.get(get_options)?.credential(), request)?;
242            set_authorization(request, HeaderValue::from_str(&authorization).unwrap());
243            Ok(())
244        }
245    }
246
247    #[cfg(feature = "async")]
248    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
249    fn async_sign<'a>(&'a self, request: &'a mut AsyncRequest<'_>) -> BoxFuture<'a, AuthorizationResult<()>> {
250        return Box::pin(async move {
251            _sign(
252                &self.provider,
253                self.timestamp_signature_enabled,
254                request,
255                Default::default(),
256            )
257            .await?;
258            Ok(())
259        });
260
261        async fn _sign(
262            credential_provider: impl CredentialProvider + Clone,
263            timestamp_signature_enabled: bool,
264            request: &mut AsyncRequest<'_>,
265            get_options: GetOptions,
266        ) -> AuthorizationResult<()> {
267            if timestamp_signature_enabled {
268                request.headers_mut().insert(X_QINIU_DATE, make_x_qiniu_date_value());
269            }
270            let authorization = authorization_v2_for_async_request(
271                credential_provider.async_get(get_options).await?.credential(),
272                request,
273            )
274            .await?;
275            set_authorization(request, HeaderValue::from_str(&authorization).unwrap());
276            Ok(())
277        }
278    }
279}
280
281fn authorization_v2_for_request(credential: &Credential, request: &mut SyncRequest) -> AuthorizationResult<String> {
282    let (parts, mut body) = take(request).into_parts_and_body();
283    credential
284        .authorization_v2_for_request_with_body_reader(parts.method(), parts.url(), parts.headers(), &mut body)
285        .tap(|_| {
286            *request = SyncRequest::from_parts_and_body(parts, body);
287        })
288        .map_err(|err| err.into())
289}
290
291#[cfg(feature = "async")]
292async fn authorization_v2_for_async_request(
293    credential: &Credential,
294    request: &mut AsyncRequest<'_>,
295) -> AuthorizationResult<String> {
296    let (parts, mut body) = take(request).into_parts_and_body();
297    credential
298        .authorization_v2_for_request_with_async_body_reader(parts.method(), parts.url(), parts.headers(), &mut body)
299        .await
300        .tap(|_| {
301            *request = AsyncRequest::from_parts_and_body(parts, body);
302        })
303        .map_err(|err| err.into())
304}
305
306fn set_authorization(request: &mut RequestParts, authorization: HeaderValue) {
307    request.headers_mut().insert(AUTHORIZATION, authorization);
308}
309
310fn uptoken_authorization(upload_token: &str) -> String {
311    "UpToken ".to_owned() + upload_token
312}
313
314const X_QINIU_DATE: &str = "X-Qiniu-Date";
315
316fn make_x_qiniu_date_value() -> HeaderValue {
317    HeaderValue::from_str(&Utc::now().format("%Y%m%dT%H%M%SZ").to_string()).unwrap()
318}
319
320/// 七牛下载地址鉴权签名
321#[derive(Clone, Debug)]
322pub struct DownloadUrlCredentialAuthorization<P: ?Sized> {
323    lifetime: Duration,
324    provider: P,
325}
326
327impl<P> DownloadUrlCredentialAuthorization<P> {
328    /// 创建七牛下载地址鉴权签名
329    #[inline]
330    pub fn new(provider: P, lifetime: Duration) -> Self {
331        Self { provider, lifetime }
332    }
333}
334
335impl<P> From<P> for DownloadUrlCredentialAuthorization<P> {
336    #[inline]
337    fn from(provider: P) -> Self {
338        Self::new(provider, Duration::from_secs(3600))
339    }
340}
341
342impl<P: CredentialProvider + Clone> AuthorizationProvider for DownloadUrlCredentialAuthorization<P> {
343    fn sign(&self, request: &mut SyncRequest) -> AuthorizationResult<()> {
344        let credential = self.provider.get(Default::default())?;
345        let url = sign_download_url(&credential, self.lifetime, take(request.url_mut()));
346        *request.url_mut() = url;
347        Ok(())
348    }
349
350    #[cfg(feature = "async")]
351    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
352    fn async_sign<'a>(&'a self, request: &'a mut AsyncRequest<'_>) -> BoxFuture<'a, AuthorizationResult<()>> {
353        Box::pin(async move {
354            let credential = self.provider.async_get(Default::default()).await?;
355            let url = sign_download_url(&credential, self.lifetime, take(request.url_mut()));
356            *request.url_mut() = url;
357            Ok(())
358        })
359    }
360}
361
362fn sign_download_url(credential: &Credential, lifetime: Duration, url: Uri) -> Uri {
363    credential.sign_download_url(url, lifetime)
364}
365
366/// 鉴权签名错误
367#[derive(Error, Debug)]
368#[non_exhaustive]
369pub enum AuthorizationError {
370    /// 获取认证信息或上传凭证错误
371    #[error("Get Upload Token or Credential error: {0}")]
372    IoError(#[from] IoError),
373
374    /// 生成上传凭证回调函数错误
375    #[error("Generate Upload Policy Callback error: {0}")]
376    CallbackError(#[from] AnyError),
377
378    /// URL 解析错误
379    #[error("Parse URL error: {0}")]
380    UrlParseError(#[from] UrlParseError),
381}
382
383/// 鉴权签名结果
384pub type AuthorizationResult<T> = Result<T, AuthorizationError>;
385
386impl From<ToStringError> for AuthorizationError {
387    fn from(err: ToStringError) -> Self {
388        match err {
389            ToStringError::CredentialGetError(err) => Self::IoError(err),
390            ToStringError::CallbackError(err) => Self::CallbackError(err),
391            err => unimplemented!("Unexpected ToStringError: {:?}", err),
392        }
393    }
394}
395
396/// 七牛鉴权签名
397///
398/// 该类型是个枚举类型,引用或拥有七牛鉴权签名接口的实例
399#[derive(Clone, Debug)]
400pub enum Authorization<'a> {
401    /// 拥有七牛鉴权签名接口的实例
402    Owned(Box<dyn AuthorizationProvider + 'a>),
403
404    /// 引用七牛鉴权签名接口的实例
405    Borrowed(&'a dyn AuthorizationProvider),
406}
407
408impl<'a> Authorization<'a> {
409    /// 根据一个拥有的七牛鉴权签名接口的实例创建一个鉴权签名
410    #[inline]
411    pub fn from_owned<T: AuthorizationProvider + 'a>(provider: T) -> Self {
412        Self::Owned(Box::new(provider))
413    }
414
415    /// 根据一个引用的七牛鉴权签名接口的实例创建一个鉴权签名
416    #[inline]
417    pub fn from_referenced(provider: &'a dyn AuthorizationProvider) -> Self {
418        Self::Borrowed(provider)
419    }
420
421    /// 根据上传凭证获取接口创建一个上传凭证签名算法的签名
422    #[inline]
423    pub fn uptoken(provider: impl UploadTokenProvider + Clone + 'a) -> Self {
424        Self::from_owned(UploadTokenAuthorization::from(provider))
425    }
426
427    /// 根据认证信息获取接口创建一个使用七牛鉴权 v1 签名算法的签名
428    #[inline]
429    pub fn v1(provider: impl CredentialProvider + Clone + 'a) -> Self {
430        Self::from_owned(CredentialAuthorizationV1::from(provider))
431    }
432
433    /// 根据认证信息获取接口创建一个使用七牛鉴权 v2 签名算法的签名
434    #[inline]
435    pub fn v2(provider: impl CredentialProvider + Clone + 'a) -> Self {
436        Self::from_owned(CredentialAuthorizationV2::from(provider))
437    }
438
439    /// 根据认证信息获取接口创建一个使用七牛鉴权 v2 签名算法的签名,并且禁用时间戳签名
440    #[inline]
441    pub fn v2_without_timestamp_signature(provider: impl CredentialProvider + Clone + 'a) -> Self {
442        let mut auth = CredentialAuthorizationV2::from(provider);
443        auth.disable_timestamp_signature();
444        Self::from_owned(auth)
445    }
446
447    /// 根据认证信息获取接口创建一个下载凭证签名算法的签名
448    #[inline]
449    pub fn download(provider: impl CredentialProvider + Clone + 'a) -> Self {
450        Self::from_owned(DownloadUrlCredentialAuthorization::from(provider))
451    }
452}
453
454impl<'a> AsRef<dyn AuthorizationProvider + 'a> for Authorization<'a> {
455    #[inline]
456    fn as_ref(&self) -> &(dyn AuthorizationProvider + 'a) {
457        match self {
458            Authorization::Owned(owned) => owned.as_ref(),
459            Authorization::Borrowed(borrowed) => borrowed,
460        }
461    }
462}
463
464impl AuthorizationProvider for Authorization<'_> {
465    fn sign(&self, request: &mut SyncRequest) -> AuthorizationResult<()> {
466        self.as_ref().sign(request)
467    }
468
469    #[cfg(feature = "async")]
470    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
471    fn async_sign<'a>(&'a self, request: &'a mut AsyncRequest<'_>) -> BoxFuture<'a, AuthorizationResult<()>> {
472        self.as_ref().async_sign(request)
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479    use anyhow::Result as AnyResult;
480    use qiniu_credential::HeaderMap;
481    use qiniu_http::SyncRequestBody;
482
483    #[test]
484    fn test_credential_authorition_v2() -> AnyResult<()> {
485        let credential = Credential::new("ak", "sk");
486        let headers = {
487            let mut headers = HeaderMap::new();
488            headers.insert("x-qiniu-", HeaderValue::from_static("a"));
489            headers.insert("x-qiniu", HeaderValue::from_static("b"));
490            headers.insert(
491                CONTENT_TYPE,
492                HeaderValue::from_static("application/x-www-form-urlencoded"),
493            );
494            headers
495        };
496        let body = b"{\"name\": \"test\"}";
497        let mut request = SyncRequest::builder()
498            .url("http://upload.qiniup.com".parse()?)
499            .headers(headers.to_owned())
500            .body(SyncRequestBody::from_bytes(body.to_vec()))
501            .build();
502        global_enable_timestamp_signature();
503        Authorization::v2(credential.to_owned()).sign(&mut request)?;
504        assert!(request
505            .headers()
506            .get(AUTHORIZATION)
507            .unwrap()
508            .to_str()?
509            .starts_with("Qiniu ak:"));
510        assert!(request.headers().get("x-qiniu-date").is_some());
511
512        global_disable_timestamp_signature();
513        request = SyncRequest::builder()
514            .url("http://upload.qiniup.com".parse()?)
515            .headers(headers)
516            .body(SyncRequestBody::from_bytes(body.to_vec()))
517            .build();
518        Authorization::v2(credential.to_owned()).sign(&mut request)?;
519        global_enable_timestamp_signature();
520        assert!(request
521            .headers()
522            .get(AUTHORIZATION)
523            .unwrap()
524            .to_str()?
525            .starts_with("Qiniu ak:"));
526        assert!(request.headers().get("x-qiniu-date").is_none());
527
528        let headers = {
529            let mut headers = HeaderMap::new();
530            headers.insert("x-qiniu-bbb", HeaderValue::from_static("AAA"));
531            headers.insert("x-qiniu-aaa", HeaderValue::from_static("CCC"));
532            headers
533        };
534        let body = b"name=test&language=go}";
535        global_disable_timestamp_signature();
536        request = SyncRequest::builder()
537            .url("http://upload.qiniup.com/mkfile/sdf.jpg".parse()?)
538            .headers(headers)
539            .body(SyncRequestBody::from_bytes(body.to_vec()))
540            .build();
541        Authorization::v2(credential).sign(&mut request)?;
542        global_enable_timestamp_signature();
543        assert_eq!(
544            request.headers().get(AUTHORIZATION).unwrap(),
545            HeaderValue::from_static("Qiniu ak:arPKqUn6T6DrnHhygbFS40PGBgY=")
546        );
547        assert!(request.headers().get("x-qiniu-date").is_none());
548        Ok(())
549    }
550}