qiniu_http_client/client/
authorization.rs1use 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#[clonable]
29#[auto_impl(&, &mut, Box, Rc, Arc)]
30pub trait AuthorizationProvider: Clone + Debug + Sync + Send {
31 fn sign(&self, request: &mut SyncRequest) -> AuthorizationResult<()>;
35
36 #[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#[derive(Clone, Debug)]
44pub struct UploadTokenAuthorization<P: ?Sized>(P);
45
46impl<P> UploadTokenAuthorization<P> {
47 #[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#[derive(Clone, Debug)]
81pub struct CredentialAuthorizationV1<P: ?Sized>(P);
82
83impl<P> CredentialAuthorizationV1<P> {
84 #[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
164pub fn global_disable_timestamp_signature() {
166 set_var(DISABLE_QINIU_TIMESTAMP_SIGNATURE, "1");
167}
168
169pub fn global_enable_timestamp_signature() {
171 remove_var(DISABLE_QINIU_TIMESTAMP_SIGNATURE);
172}
173
174#[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 #[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 #[inline]
199 pub fn disable_timestamp_signature(&mut self) -> &mut Self {
200 self.timestamp_signature_enabled = false;
201 self
202 }
203
204 #[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#[derive(Clone, Debug)]
322pub struct DownloadUrlCredentialAuthorization<P: ?Sized> {
323 lifetime: Duration,
324 provider: P,
325}
326
327impl<P> DownloadUrlCredentialAuthorization<P> {
328 #[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#[derive(Error, Debug)]
368#[non_exhaustive]
369pub enum AuthorizationError {
370 #[error("Get Upload Token or Credential error: {0}")]
372 IoError(#[from] IoError),
373
374 #[error("Generate Upload Policy Callback error: {0}")]
376 CallbackError(#[from] AnyError),
377
378 #[error("Parse URL error: {0}")]
380 UrlParseError(#[from] UrlParseError),
381}
382
383pub 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#[derive(Clone, Debug)]
400pub enum Authorization<'a> {
401 Owned(Box<dyn AuthorizationProvider + 'a>),
403
404 Borrowed(&'a dyn AuthorizationProvider),
406}
407
408impl<'a> Authorization<'a> {
409 #[inline]
411 pub fn from_owned<T: AuthorizationProvider + 'a>(provider: T) -> Self {
412 Self::Owned(Box::new(provider))
413 }
414
415 #[inline]
417 pub fn from_referenced(provider: &'a dyn AuthorizationProvider) -> Self {
418 Self::Borrowed(provider)
419 }
420
421 #[inline]
423 pub fn uptoken(provider: impl UploadTokenProvider + Clone + 'a) -> Self {
424 Self::from_owned(UploadTokenAuthorization::from(provider))
425 }
426
427 #[inline]
429 pub fn v1(provider: impl CredentialProvider + Clone + 'a) -> Self {
430 Self::from_owned(CredentialAuthorizationV1::from(provider))
431 }
432
433 #[inline]
435 pub fn v2(provider: impl CredentialProvider + Clone + 'a) -> Self {
436 Self::from_owned(CredentialAuthorizationV2::from(provider))
437 }
438
439 #[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 #[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}