1#![cfg_attr(feature = "docs", feature(doc_cfg))]
2#![deny(
3 single_use_lifetimes,
4 missing_debug_implementations,
5 large_assignments,
6 exported_private_dependencies,
7 absolute_paths_not_starting_with_crate,
8 anonymous_parameters,
9 explicit_outlives_requirements,
10 keyword_idents,
11 macro_use_extern_crate,
12 meta_variable_misuse,
13 missing_docs,
14 non_ascii_idents,
15 indirect_structural_match,
16 trivial_numeric_casts,
17 unreachable_pub,
18 unsafe_code,
19 unused_crate_dependencies,
20 unused_extern_crates,
21 unused_import_braces,
22 unused_lifetimes,
23 unused_qualifications
24)]
25
26use assert_impl::assert_impl;
99use auto_impl::auto_impl;
100use dyn_clonable::clonable;
101use hmac::{Hmac, Mac, NewMac};
102use http::header::CONTENT_TYPE;
103pub use http::{
104 header::{HeaderMap, HeaderName, HeaderValue},
105 method::Method,
106 uri::Uri,
107 Extensions,
108};
109use mime::{APPLICATION_OCTET_STREAM, APPLICATION_WWW_FORM_URLENCODED};
110use once_cell::sync::Lazy;
111use qiniu_utils::base64;
112use sha1::Sha1;
113use std::{
114 collections::VecDeque,
115 env,
116 fmt::{self, Debug},
117 io::{copy, Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult},
118 mem::take,
119 ops::{Deref, DerefMut},
120 sync::{Arc, RwLock},
121 time::{Duration, SystemTime, UNIX_EPOCH},
122};
123
124mod header_name;
125use header_name::make_header_name;
126
127mod key;
128pub use key::{AccessKey, SecretKey};
129
130pub mod prelude {
132 pub use super::CredentialProvider;
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
139pub struct Credential {
140 access_key: AccessKey,
141 secret_key: SecretKey,
142}
143
144impl Credential {
145 #[inline]
147 pub fn new(access_key: impl Into<AccessKey>, secret_key: impl Into<SecretKey>) -> Self {
148 Self {
149 access_key: access_key.into(),
150 secret_key: secret_key.into(),
151 }
152 }
153
154 #[inline]
156 pub fn access_key(&self) -> &AccessKey {
157 &self.access_key
158 }
159
160 #[inline]
162 pub fn secret_key(&self) -> &SecretKey {
163 &self.secret_key
164 }
165
166 #[inline]
168 pub fn split(self) -> (AccessKey, SecretKey) {
169 (self.access_key, self.secret_key)
170 }
171
172 pub fn sign(&self, data: &[u8]) -> String {
188 self.sign_within::<IoError, _>(|hmac| {
189 hmac.update(data);
190 Ok(())
191 })
192 .unwrap()
193 }
194
195 pub fn sign_reader(&self, reader: &mut dyn Read) -> IoResult<String> {
216 self.sign_within(|hmac| copy(reader, hmac).map(|_| ()))
217 }
218
219 fn sign_within<E, F: FnOnce(&mut Hmac<Sha1>) -> Result<(), E>>(&self, f: F) -> Result<String, E> {
220 let signature = generate_base64ed_hmac_sha1_digest_within(self.secret_key(), f)?;
221 Ok(self.access_key().to_string() + ":" + &signature)
222 }
223
224 pub fn sign_with_data(&self, data: &[u8]) -> String {
241 let encoded_data = base64::urlsafe(data);
242 self.sign(encoded_data.as_bytes()) + ":" + &encoded_data
243 }
244
245 pub fn authorization_v1_for_request(&self, url: &Uri, content_type: Option<&HeaderValue>, body: &[u8]) -> String {
264 let authorization_token = sign_request_v1(self, url, content_type, body);
265 "QBox ".to_owned() + &authorization_token
266 }
267
268 pub fn authorization_v1_for_request_with_body_reader(
289 &self,
290 url: &Uri,
291 content_type: Option<&HeaderValue>,
292 body: &mut dyn Read,
293 ) -> IoResult<String> {
294 let authorization_token = sign_request_v1_with_body_reader(self, url, content_type, body)?;
295 Ok("QBox ".to_owned() + &authorization_token)
296 }
297
298 pub fn authorization_v2_for_request(&self, method: &Method, url: &Uri, headers: &HeaderMap, body: &[u8]) -> String {
320 let authorization_token = sign_request_v2(self, method, url, headers, body);
321 "Qiniu ".to_owned() + &authorization_token
322 }
323
324 pub fn authorization_v2_for_request_with_body_reader(
349 &self,
350 method: &Method,
351 url: &Uri,
352 headers: &HeaderMap,
353 body: &mut dyn Read,
354 ) -> IoResult<String> {
355 let authorization_token = sign_request_v2_with_body_reader(self, method, url, headers, body)?;
356 Ok("Qiniu ".to_owned() + &authorization_token)
357 }
358
359 pub fn sign_download_url(&self, url: Uri, lifetime: Duration) -> Uri {
375 let deadline = SystemTime::now() + lifetime;
376 let deadline = deadline
377 .duration_since(UNIX_EPOCH)
378 .expect("Invalid UNIX Timestamp")
379 .as_secs();
380 let to_sign = append_query_pairs_to_url(url, &[("e", &deadline.to_string())]);
381 let signature = self.sign(to_sign.to_string().as_bytes());
382 return append_query_pairs_to_url(to_sign, &[("token", &signature)]);
383
384 fn append_query_pairs_to_url(url: Uri, pairs: &[(&str, &str)]) -> Uri {
385 let path_string = url.path().to_owned();
386 let query_string = url.query().unwrap_or_default().to_owned();
387 let mut serializer = form_urlencoded::Serializer::new(query_string);
388 for (key, value) in pairs.iter() {
389 serializer.append_pair(key, value);
390 }
391 let query_string = serializer.finish();
392 let mut path_and_query = path_string;
393 if !query_string.is_empty() {
394 path_and_query.push('?');
395 path_and_query.push_str(&query_string);
396 }
397 let parts = url.into_parts();
398 let mut builder = Uri::builder();
399 if let Some(scheme) = parts.scheme {
400 builder = builder.scheme(scheme);
401 }
402 if let Some(authority) = parts.authority {
403 builder = builder.authority(authority);
404 }
405 builder.path_and_query(&path_and_query).build().unwrap()
406 }
407 }
408
409 #[allow(dead_code)]
410 fn assert() {
411 assert_impl!(Send: Self);
412 assert_impl!(Sync: Self);
413 }
414}
415
416#[cfg(feature = "async")]
417impl Credential {
418 #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
437 pub async fn sign_async_reader(&self, reader: &mut (dyn AsyncRead + Send + Unpin)) -> IoResult<String> {
438 let mut hmac = new_hmac_sha1(self.secret_key());
439 copy_async_reader_to_hmac_sha1(&mut hmac, reader).await?;
440 Ok(base64ed_hmac_sha1_with_access_key(self.access_key().to_string(), hmac))
441 }
442
443 #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
462 pub async fn authorization_v1_for_request_with_async_body_reader(
463 &self,
464 url: &Uri,
465 content_type: Option<&HeaderValue>,
466 body: &mut (dyn AsyncRead + Send + Unpin),
467 ) -> IoResult<String> {
468 let authorization_token = sign_request_v1_with_async_body_reader(self, url, content_type, body).await?;
469 Ok("QBox ".to_owned() + &authorization_token)
470 }
471
472 #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
496 pub async fn authorization_v2_for_request_with_async_body_reader(
497 &self,
498 method: &Method,
499 url: &Uri,
500 headers: &HeaderMap,
501 body: &mut (dyn AsyncRead + Send + Unpin),
502 ) -> IoResult<String> {
503 let authorization_token = sign_request_v2_with_async_body_reader(self, method, url, headers, body).await?;
504 Ok("Qiniu ".to_owned() + &authorization_token)
505 }
506}
507
508fn sign_request_v1(cred: &Credential, url: &Uri, content_type: Option<&HeaderValue>, body: &[u8]) -> String {
509 cred.sign_within::<IoError, _>(|hmac| {
510 _sign_request_v1_without_body(hmac, url);
511 if let Some(content_type) = content_type {
512 if !body.is_empty() && will_push_body_v1(content_type) {
513 hmac.update(body);
514 }
515 }
516 Ok(())
517 })
518 .unwrap()
519}
520
521fn sign_request_v1_with_body_reader(
522 cred: &Credential,
523 url: &Uri,
524 content_type: Option<&HeaderValue>,
525 body: &mut dyn Read,
526) -> IoResult<String> {
527 cred.sign_within(|hmac| {
528 _sign_request_v1_without_body(hmac, url);
529 if let Some(content_type) = content_type {
530 if will_push_body_v1(content_type) {
531 copy(body, hmac)?;
532 }
533 }
534 Ok(())
535 })
536}
537
538fn _sign_request_v1_without_body(digest: &mut Hmac<Sha1>, url: &Uri) {
539 digest.update(url.path().as_bytes());
540 if let Some(query) = url.query() {
541 if !query.is_empty() {
542 digest.update(b"?");
543 digest.update(query.as_bytes());
544 }
545 }
546 digest.update(b"\n");
547}
548
549fn sign_request_v2(cred: &Credential, method: &Method, url: &Uri, headers: &HeaderMap, body: &[u8]) -> String {
550 cred.sign_within::<IoError, _>(|hmac| {
551 _sign_request_v2_without_body(hmac, method, url, headers);
552 if let Some(content_type) = headers.get(CONTENT_TYPE) {
553 if will_push_body_v2(content_type) {
554 hmac.update(body);
555 }
556 }
557 Ok(())
558 })
559 .unwrap()
560}
561
562fn sign_request_v2_with_body_reader(
563 cred: &Credential,
564 method: &Method,
565 url: &Uri,
566 headers: &HeaderMap,
567 body: &mut dyn Read,
568) -> IoResult<String> {
569 cred.sign_within(|hmac| {
570 _sign_request_v2_without_body(hmac, method, url, headers);
571 if let Some(content_type) = headers.get(CONTENT_TYPE) {
572 if will_push_body_v2(content_type) {
573 copy(body, hmac)?;
574 }
575 }
576 Ok(())
577 })
578}
579
580fn _sign_request_v2_without_body(digest: &mut Hmac<Sha1>, method: &Method, url: &Uri, headers: &HeaderMap) {
581 digest.update(method.as_str().as_bytes());
582 digest.update(b" ");
583 digest.update(url.path().as_bytes());
584 if let Some(query) = url.query() {
585 if !query.is_empty() {
586 digest.update(b"?");
587 digest.update(query.as_bytes());
588 }
589 }
590 if let Some(host) = url.host() {
591 digest.update(b"\nHost: ");
592 digest.update(host.as_bytes());
593 }
594 if let Some(port) = url.port() {
595 digest.update(b":");
596 digest.update(port.to_string().as_bytes());
597 }
598 digest.update(b"\n");
599
600 if let Some(content_type) = headers.get(CONTENT_TYPE) {
601 digest.update(b"Content-Type: ");
602 digest.update(content_type.as_bytes());
603 digest.update(b"\n");
604 }
605 _sign_data_for_x_qiniu_headers(digest, headers);
606 digest.update(b"\n");
607 return;
608
609 fn _sign_data_for_x_qiniu_headers(digest: &mut Hmac<Sha1>, headers: &HeaderMap) {
610 let mut x_qiniu_headers = headers
611 .iter()
612 .map(|(key, value)| (make_header_name(key.as_str().into()), value.as_bytes()))
613 .filter(|(key, _)| key.len() > "X-Qiniu-".len())
614 .filter(|(key, _)| key.starts_with("X-Qiniu-"))
615 .collect::<Vec<_>>();
616 if x_qiniu_headers.is_empty() {
617 return;
618 }
619 x_qiniu_headers.sort_unstable();
620 for (header_key, header_value) in x_qiniu_headers {
621 digest.update(header_key.as_bytes());
622 digest.update(b": ");
623 digest.update(header_value);
624 digest.update(b"\n");
625 }
626 }
627}
628
629fn generate_base64ed_hmac_sha1_digest_within<E, F: FnOnce(&mut Hmac<Sha1>) -> Result<(), E>>(
630 secret_key: &str,
631 f: F,
632) -> Result<String, E> {
633 let mut hmac = new_hmac_sha1(secret_key);
634 f(&mut hmac)?;
635 Ok(base64ed_hmac_sha1(hmac))
636}
637
638fn new_hmac_sha1(secret_key: &str) -> Hmac<Sha1> {
639 Hmac::<Sha1>::new_from_slice(secret_key.as_bytes()).unwrap()
640}
641
642fn base64ed_hmac_sha1(hmac: Hmac<Sha1>) -> String {
643 base64::urlsafe(&hmac.finalize().into_bytes())
644}
645
646#[cfg(feature = "async")]
647fn base64ed_hmac_sha1_with_access_key(access_key: String, hmac: Hmac<Sha1>) -> String {
648 access_key + ":" + &base64ed_hmac_sha1(hmac)
649}
650
651fn will_push_body_v1(content_type: &HeaderValue) -> bool {
652 APPLICATION_WWW_FORM_URLENCODED.as_ref() == content_type
653}
654
655fn will_push_body_v2(content_type: &HeaderValue) -> bool {
656 APPLICATION_OCTET_STREAM.as_ref() != content_type
657}
658
659#[cfg(feature = "async")]
660mod async_sign {
661 use super::*;
662 use futures_lite::io::AsyncRead;
663 use std::task::{Context, Poll};
664
665 pub(super) async fn sign_request_v1_with_async_body_reader(
666 cred: &Credential,
667 url: &Uri,
668 content_type: Option<&HeaderValue>,
669 body: &mut (dyn AsyncRead + Send + Unpin),
670 ) -> IoResult<String> {
671 let mut hmac = new_hmac_sha1(cred.secret_key());
672 _sign_request_v1_without_body(&mut hmac, url);
673 if let Some(content_type) = content_type {
674 if will_push_body_v1(content_type) {
675 copy_async_reader_to_hmac_sha1(&mut hmac, body).await?;
676 }
677 }
678 Ok(base64ed_hmac_sha1_with_access_key(cred.access_key().to_string(), hmac))
679 }
680
681 pub(super) async fn sign_request_v2_with_async_body_reader(
682 cred: &Credential,
683 method: &Method,
684 url: &Uri,
685 headers: &HeaderMap,
686 body: &mut (dyn AsyncRead + Send + Unpin),
687 ) -> IoResult<String> {
688 let mut hmac = new_hmac_sha1(cred.secret_key());
689 _sign_request_v2_without_body(&mut hmac, method, url, headers);
690 if let Some(content_type) = headers.get(CONTENT_TYPE) {
691 if will_push_body_v2(content_type) {
692 copy_async_reader_to_hmac_sha1(&mut hmac, body).await?;
693 }
694 }
695 Ok(base64ed_hmac_sha1_with_access_key(cred.access_key().to_string(), hmac))
696 }
697
698 pub(super) async fn copy_async_reader_to_hmac_sha1(
699 hmac: &mut Hmac<Sha1>,
700 reader: &mut (dyn AsyncRead + Send + Unpin),
701 ) -> IoResult<u64> {
702 use futures_lite::io::{copy as async_io_copy, AsyncWrite};
703
704 struct AsyncHmacWriter<'a>(&'a mut Hmac<Sha1>);
705
706 impl AsyncWrite for AsyncHmacWriter<'_> {
707 #[inline]
708 fn poll_write(self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8]) -> Poll<IoResult<usize>> {
709 #[allow(unsafe_code)]
710 unsafe { self.get_unchecked_mut() }.0.update(buf);
711 Poll::Ready(Ok(buf.len()))
712 }
713
714 #[inline]
715 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
716 Poll::Ready(Ok(()))
717 }
718
719 #[inline]
720 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
721 Poll::Ready(Ok(()))
722 }
723 }
724
725 async_io_copy(reader, &mut AsyncHmacWriter(hmac)).await
726 }
727}
728
729#[cfg(feature = "async")]
730#[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
731pub use futures_lite::AsyncRead;
732
733#[cfg(feature = "async")]
734use {
735 async_sign::*,
736 std::{future::Future, pin::Pin},
737};
738
739#[cfg(feature = "async")]
740type AsyncIoResult<'a, T> = Pin<Box<dyn Future<Output = IoResult<T>> + 'a + Send>>;
741
742#[clonable]
744#[auto_impl(&, &mut, Box, Rc, Arc)]
745pub trait CredentialProvider: Clone + Debug + Sync + Send {
746 fn get(&self, opts: GetOptions) -> IoResult<GotCredential>;
750
751 #[inline]
753 #[cfg(feature = "async")]
754 #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
755 fn async_get(&self, opts: GetOptions) -> AsyncIoResult<'_, GotCredential> {
756 Box::pin(async move { self.get(opts) })
757 }
758}
759
760impl CredentialProvider for Credential {
761 #[inline]
762 fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
763 Ok(self.to_owned().into())
764 }
765}
766
767#[derive(Copy, Clone, Debug, Default)]
769pub struct GetOptions {}
770
771#[derive(Clone, Debug, PartialEq, Eq)]
775pub struct GotCredential(Credential);
776
777impl From<GotCredential> for Credential {
778 #[inline]
779 fn from(result: GotCredential) -> Self {
780 result.into_credential()
781 }
782}
783
784impl From<Credential> for GotCredential {
785 #[inline]
786 fn from(credential: Credential) -> Self {
787 Self(credential)
788 }
789}
790
791impl GotCredential {
792 #[inline]
794 pub fn credential(&self) -> &Credential {
795 &self.0
796 }
797
798 #[inline]
800 pub fn credential_mut(&mut self) -> &mut Credential {
801 &mut self.0
802 }
803
804 #[inline]
806 pub fn into_credential(self) -> Credential {
807 self.0
808 }
809}
810
811impl Deref for GotCredential {
812 type Target = Credential;
813
814 #[inline]
815 fn deref(&self) -> &Self::Target {
816 &self.0
817 }
818}
819
820impl DerefMut for GotCredential {
821 #[inline]
822 fn deref_mut(&mut self) -> &mut Self::Target {
823 &mut self.0
824 }
825}
826
827impl CredentialProvider for GotCredential {
828 #[inline]
829 fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
830 Ok(self.to_owned())
831 }
832}
833
834#[derive(Copy, Clone, Eq, PartialEq)]
836pub struct GlobalCredentialProvider;
837
838static GLOBAL_CREDENTIAL: Lazy<RwLock<Option<Credential>>> = Lazy::new(|| RwLock::new(None));
839
840impl GlobalCredentialProvider {
841 #[inline]
843 pub fn setup(credential: Credential) {
844 let mut global_credential = GLOBAL_CREDENTIAL.write().unwrap();
845 *global_credential = Some(credential);
846 }
847
848 #[inline]
850 pub fn clear() {
851 let mut global_credential = GLOBAL_CREDENTIAL.write().unwrap();
852 *global_credential = None;
853 }
854}
855
856impl CredentialProvider for GlobalCredentialProvider {
857 #[inline]
858 fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
859 if let Some(credential) = GLOBAL_CREDENTIAL.read().unwrap().as_ref() {
860 Ok(credential.to_owned().into())
861 } else {
862 Err(IoError::new(
863 IoErrorKind::Other,
864 "GlobalCredentialProvider is not setuped, please call GlobalCredentialProvider::setup() to do it",
865 ))
866 }
867 }
868}
869
870impl Debug for GlobalCredentialProvider {
871 #[inline]
872 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
873 let mut d = f.debug_struct("GlobalCredentialProvider");
874 d.field("credential", &GLOBAL_CREDENTIAL.read().unwrap());
875 d.finish()
876 }
877}
878
879#[derive(Copy, Clone, Eq, PartialEq)]
881pub struct EnvCredentialProvider;
882
883pub const QINIU_ACCESS_KEY_ENV_KEY: &str = "QINIU_ACCESS_KEY";
885pub const QINIU_SECRET_KEY_ENV_KEY: &str = "QINIU_SECRET_KEY";
887
888impl EnvCredentialProvider {
889 #[inline]
891 pub fn setup(credential: &Credential) {
892 env::set_var(QINIU_ACCESS_KEY_ENV_KEY, credential.access_key().as_str());
893 env::set_var(QINIU_SECRET_KEY_ENV_KEY, credential.secret_key().as_str());
894 }
895
896 #[inline]
898 pub fn clear() {
899 env::remove_var(QINIU_ACCESS_KEY_ENV_KEY);
900 env::remove_var(QINIU_SECRET_KEY_ENV_KEY);
901 }
902}
903
904impl CredentialProvider for EnvCredentialProvider {
905 fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
906 match (env::var(QINIU_ACCESS_KEY_ENV_KEY), env::var(QINIU_SECRET_KEY_ENV_KEY)) {
907 (Ok(access_key), Ok(secret_key)) if !access_key.is_empty() && !secret_key.is_empty() => {
908 Ok(Credential::new(access_key, secret_key).into())
909 }
910 _ => {
911 static ERROR_MESSAGE: Lazy<String> = Lazy::new(|| {
912 format!("EnvCredentialProvider is not setuped, please call EnvCredentialProvider::setup() to do it, or set environment variable `{QINIU_ACCESS_KEY_ENV_KEY}` and `{QINIU_SECRET_KEY_ENV_KEY}`")
913 });
914 Err(IoError::new(IoErrorKind::Other, ERROR_MESSAGE.as_str()))
915 }
916 }
917 }
918}
919
920impl Debug for EnvCredentialProvider {
921 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
922 let mut d = f.debug_struct("EnvCredentialProvider");
923 if let (Some(access_key), Some(secret_key)) = (
924 env::var_os(QINIU_ACCESS_KEY_ENV_KEY),
925 env::var_os(QINIU_SECRET_KEY_ENV_KEY),
926 ) {
927 d.field("access_key", &access_key).field("secret_key", &secret_key);
928 }
929 d.finish()
930 }
931}
932
933#[derive(Clone, Debug)]
937pub struct ChainCredentialsProvider {
938 credentials: Arc<[Box<dyn CredentialProvider>]>,
939}
940
941impl ChainCredentialsProvider {
942 #[inline]
944 pub fn builder(credential: impl CredentialProvider + 'static) -> ChainCredentialsProviderBuilder {
945 ChainCredentialsProviderBuilder::new(credential)
946 }
947}
948
949impl CredentialProvider for ChainCredentialsProvider {
950 fn get(&self, opts: GetOptions) -> IoResult<GotCredential> {
951 let mut last_err = None;
952 if let Some(credential) = self.credentials.iter().find_map(|c| match c.get(opts) {
953 Ok(cred) => Some(cred),
954 Err(err) => {
955 last_err = Some(err);
956 None
957 }
958 }) {
959 Ok(credential)
960 } else {
961 Err(last_err.expect("No credential in ChainCredentialsProvider, which is unexpected"))
962 }
963 }
964
965 #[cfg(feature = "async")]
966 #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
967 fn async_get(&self, opts: GetOptions) -> AsyncIoResult<'_, GotCredential> {
968 Box::pin(async move {
969 let mut last_err = None;
970 for provider in self.credentials.iter() {
971 match provider.async_get(opts).await {
972 Ok(cred) => {
973 return Ok(cred);
974 }
975 Err(err) => {
976 last_err = Some(err);
977 }
978 }
979 }
980 Err(last_err.expect("No credential in ChainCredentialsProvider, which is unexpected"))
981 })
982 }
983}
984
985impl Default for ChainCredentialsProvider {
986 #[inline]
987 fn default() -> Self {
988 ChainCredentialsProviderBuilder::new(Box::new(GlobalCredentialProvider))
989 .append_credential(Box::new(EnvCredentialProvider))
990 .build()
991 }
992}
993
994impl FromIterator<Box<dyn CredentialProvider>> for ChainCredentialsProvider {
995 #[inline]
996 fn from_iter<T: IntoIterator<Item = Box<dyn CredentialProvider>>>(iter: T) -> Self {
997 ChainCredentialsProviderBuilder::from_iter(iter).build()
998 }
999}
1000
1001impl<'a> IntoIterator for &'a ChainCredentialsProvider {
1002 type Item = &'a Box<dyn CredentialProvider + 'static>;
1003 type IntoIter = std::slice::Iter<'a, Box<dyn CredentialProvider + 'static>>;
1004
1005 #[inline]
1006 fn into_iter(self) -> Self::IntoIter {
1007 self.credentials.iter()
1008 }
1009}
1010
1011#[derive(Debug, Clone, Default)]
1015pub struct ChainCredentialsProviderBuilder {
1016 credentials: VecDeque<Box<dyn CredentialProvider + 'static>>,
1017}
1018
1019impl ChainCredentialsProviderBuilder {
1020 #[inline]
1022 pub fn new(credential: impl CredentialProvider + 'static) -> Self {
1023 let mut builder = Self::default();
1024 builder.append_credential(credential);
1025 builder
1026 }
1027
1028 #[inline]
1030 pub fn append_credential(&mut self, credential: impl CredentialProvider + 'static) -> &mut Self {
1031 self.credentials.push_back(Box::new(credential));
1032 self
1033 }
1034
1035 #[inline]
1037 pub fn prepend_credential(&mut self, credential: impl CredentialProvider + 'static) -> &mut Self {
1038 self.credentials.push_front(Box::new(credential));
1039 self
1040 }
1041
1042 #[inline]
1044 pub fn build(&mut self) -> ChainCredentialsProvider {
1045 assert!(
1046 !self.credentials.is_empty(),
1047 "ChainCredentialsProvider must owns at least one CredentialProvider"
1048 );
1049 ChainCredentialsProvider {
1050 credentials: Vec::from(take(&mut self.credentials)).into_boxed_slice().into(),
1051 }
1052 }
1053}
1054
1055impl FromIterator<Box<dyn CredentialProvider>> for ChainCredentialsProviderBuilder {
1056 #[inline]
1057 fn from_iter<T: IntoIterator<Item = Box<dyn CredentialProvider>>>(iter: T) -> Self {
1058 ChainCredentialsProviderBuilder {
1059 credentials: VecDeque::from_iter(iter),
1060 }
1061 }
1062}
1063
1064impl Extend<Box<dyn CredentialProvider>> for ChainCredentialsProviderBuilder {
1065 #[inline]
1066 fn extend<T: IntoIterator<Item = Box<dyn CredentialProvider>>>(&mut self, iter: T) {
1067 self.credentials.extend(iter)
1068 }
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use anyhow::Result;
1075 use async_std as _;
1076 use mime::APPLICATION_JSON;
1077 use std::{io::Cursor, thread, time::Duration};
1078
1079 #[test]
1080 fn test_sign() -> Result<()> {
1081 let credential = get_credential();
1082 let mut threads = Vec::new();
1083 {
1084 let credential = credential.to_owned();
1085 threads.push(thread::spawn(move || {
1086 assert_eq!(
1087 credential.get(Default::default()).unwrap().sign(b"hello"),
1088 "abcdefghklmnopq:b84KVc-LroDiz0ebUANfdzSRxa0="
1089 );
1090 assert_eq!(
1091 credential
1092 .get(Default::default())
1093 .unwrap()
1094 .sign_reader(&mut Cursor::new(b"world"))
1095 .unwrap(),
1096 "abcdefghklmnopq:VjgXt0P_nCxHuaTfiFz-UjDJ1AQ="
1097 );
1098 }));
1099 }
1100 {
1101 threads.push(thread::spawn(move || {
1102 assert_eq!(
1103 credential.get(Default::default()).unwrap().sign(b"-test"),
1104 "abcdefghklmnopq:vYKRLUoXRlNHfpMEQeewG0zylaw="
1105 );
1106 assert_eq!(
1107 credential
1108 .get(Default::default())
1109 .unwrap()
1110 .sign_reader(&mut Cursor::new(b"ba#a-"))
1111 .unwrap(),
1112 "abcdefghklmnopq:2d_Yr6H1GdTKg3RvMtpHOhi047M="
1113 );
1114 }));
1115 }
1116 threads.into_iter().for_each(|thread| thread.join().unwrap());
1117 Ok(())
1118 }
1119
1120 #[test]
1121 fn test_sign_with_data() -> Result<()> {
1122 let credential = get_credential();
1123 let mut threads = Vec::new();
1124 {
1125 let credential = credential.to_owned();
1126 threads.push(thread::spawn(move || {
1127 assert_eq!(
1128 credential.get(Default::default()).unwrap().sign_with_data(b"hello"),
1129 "abcdefghklmnopq:BZYt5uVRy1RVt5ZTXbaIt2ROVMA=:aGVsbG8="
1130 );
1131 assert_eq!(
1132 credential.get(Default::default()).unwrap().sign_with_data(b"world"),
1133 "abcdefghklmnopq:Wpe04qzPphiSZb1u6I0nFn6KpZg=:d29ybGQ="
1134 );
1135 }));
1136 }
1137 {
1138 threads.push(thread::spawn(move || {
1139 assert_eq!(
1140 credential.get(Default::default()).unwrap().sign_with_data(b"-test"),
1141 "abcdefghklmnopq:HlxenSSP_6BbaYNzx1fyeyw8v1Y=:LXRlc3Q="
1142 );
1143 assert_eq!(
1144 credential.get(Default::default()).unwrap().sign_with_data(b"ba#a-"),
1145 "abcdefghklmnopq:kwzeJrFziPDMO4jv3DKVLDyqud0=:YmEjYS0="
1146 );
1147 }));
1148 }
1149 threads.into_iter().for_each(|thread| thread.join().unwrap());
1150 Ok(())
1151 }
1152
1153 #[test]
1154 fn test_authorization_v1_with_body_reader() -> Result<()> {
1155 let credential = get_credential();
1156 assert_eq!(
1157 credential
1158 .get(Default::default())?
1159 .authorization_v1_for_request_with_body_reader(
1160 &"http://upload.qiniup.com/".parse()?,
1161 None,
1162 &mut Cursor::new(b"{\"name\":\"test\"}")
1163 )?,
1164 "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1165 );
1166 assert_eq!(
1167 credential
1168 .get(Default::default())?
1169 .authorization_v1_for_request_with_body_reader(
1170 &"http://upload.qiniup.com/".parse()?,
1171 Some(&HeaderValue::from_str(APPLICATION_JSON.as_ref())?),
1172 &mut Cursor::new(b"{\"name\":\"test\"}")
1173 )?,
1174 "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1175 );
1176 assert_eq!(
1177 credential
1178 .get(Default::default())?
1179 .authorization_v1_for_request_with_body_reader(
1180 &"http://upload.qiniup.com/".parse()?,
1181 Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1182 &mut Cursor::new(b"name=test&language=go")
1183 )?,
1184 "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\nname=test&language=go")
1185 );
1186 assert_eq!(
1187 credential
1188 .get(Default::default())?
1189 .authorization_v1_for_request_with_body_reader(
1190 &"http://upload.qiniup.com/?v=2".parse()?,
1191 Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1192 &mut Cursor::new(b"name=test&language=go")
1193 )?,
1194 "QBox ".to_owned()
1195 + &credential
1196 .get(Default::default())?
1197 .sign(b"/?v=2\nname=test&language=go")
1198 );
1199 assert_eq!(
1200 credential
1201 .get(Default::default())?
1202 .authorization_v1_for_request_with_body_reader(
1203 &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1204 Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1205 &mut Cursor::new(b"name=test&language=go")
1206 )?,
1207 "QBox ".to_owned()
1208 + &credential
1209 .get(Default::default())?
1210 .sign(b"/find/sdk?v=2\nname=test&language=go")
1211 );
1212 Ok(())
1213 }
1214
1215 #[test]
1216 fn test_authorization_v2_with_body_reader() -> Result<()> {
1217 let credential = get_global_credential();
1218 let empty_headers = {
1219 let mut headers = HeaderMap::new();
1220 headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1221 headers
1222 };
1223 let json_headers = {
1224 let mut headers = HeaderMap::new();
1225 headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
1226 headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1227 headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1228 headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1229 headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1230 headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1231 headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1232 headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1233 headers
1234 };
1235 let form_headers = {
1236 let mut headers = HeaderMap::new();
1237 headers.insert(
1238 CONTENT_TYPE,
1239 HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?,
1240 );
1241 headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1242 headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1243 headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1244 headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1245 headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1246 headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1247 headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1248 headers
1249 };
1250 assert_eq!(
1251 credential
1252 .get(Default::default())?
1253 .authorization_v2_for_request_with_body_reader(
1254 &Method::GET,
1255 &"http://upload.qiniup.com/".parse()?,
1256 &json_headers,
1257 &mut Cursor::new(b"{\"name\":\"test\"}")
1258 )?,
1259 "Qiniu ".to_owned()
1260 + &credential.get(Default::default())?.sign(
1261 concat!(
1262 "GET /\n",
1263 "Host: upload.qiniup.com\n",
1264 "Content-Type: application/json\n",
1265 "X-Qiniu-Axxxx: valuea\n",
1266 "X-Qiniu-Bxxxx: valueb\n",
1267 "X-Qiniu-Cxxxx: valuec\n",
1268 "X-Qiniu-E: value\n\n",
1269 "{\"name\":\"test\"}"
1270 )
1271 .as_bytes()
1272 )
1273 );
1274 assert_eq!(
1275 credential
1276 .get(Default::default())?
1277 .authorization_v2_for_request_with_body_reader(
1278 &Method::GET,
1279 &"http://upload.qiniup.com/".parse()?,
1280 &empty_headers,
1281 &mut Cursor::new(b"{\"name\":\"test\"}")
1282 )?,
1283 "Qiniu ".to_owned()
1284 + &credential
1285 .get(Default::default())?
1286 .sign(concat!("GET /\n", "Host: upload.qiniup.com\n\n").as_bytes())
1287 );
1288 assert_eq!(
1289 credential
1290 .get(Default::default())?
1291 .authorization_v2_for_request_with_body_reader(
1292 &Method::POST,
1293 &"http://upload.qiniup.com/".parse()?,
1294 &json_headers,
1295 &mut Cursor::new(b"{\"name\":\"test\"}")
1296 )?,
1297 "Qiniu ".to_owned()
1298 + &credential.get(Default::default())?.sign(
1299 concat!(
1300 "POST /\n",
1301 "Host: upload.qiniup.com\n",
1302 "Content-Type: application/json\n",
1303 "X-Qiniu-Axxxx: valuea\n",
1304 "X-Qiniu-Bxxxx: valueb\n",
1305 "X-Qiniu-Cxxxx: valuec\n",
1306 "X-Qiniu-E: value\n\n",
1307 "{\"name\":\"test\"}"
1308 )
1309 .as_bytes()
1310 )
1311 );
1312 assert_eq!(
1313 credential
1314 .get(Default::default())?
1315 .authorization_v2_for_request_with_body_reader(
1316 &Method::GET,
1317 &"http://upload.qiniup.com/".parse()?,
1318 &form_headers,
1319 &mut Cursor::new(b"name=test&language=go")
1320 )?,
1321 "Qiniu ".to_owned()
1322 + &credential.get(Default::default())?.sign(
1323 concat!(
1324 "GET /\n",
1325 "Host: upload.qiniup.com\n",
1326 "Content-Type: application/x-www-form-urlencoded\n",
1327 "X-Qiniu-Axxxx: valuea\n",
1328 "X-Qiniu-Bxxxx: valueb\n",
1329 "X-Qiniu-Cxxxx: valuec\n",
1330 "X-Qiniu-E: value\n\n",
1331 "name=test&language=go"
1332 )
1333 .as_bytes()
1334 )
1335 );
1336 assert_eq!(
1337 credential
1338 .get(Default::default())?
1339 .authorization_v2_for_request_with_body_reader(
1340 &Method::GET,
1341 &"http://upload.qiniup.com/?v=2".parse()?,
1342 &form_headers,
1343 &mut Cursor::new(b"name=test&language=go")
1344 )?,
1345 "Qiniu ".to_owned()
1346 + &credential.get(Default::default())?.sign(
1347 concat!(
1348 "GET /?v=2\n",
1349 "Host: upload.qiniup.com\n",
1350 "Content-Type: application/x-www-form-urlencoded\n",
1351 "X-Qiniu-Axxxx: valuea\n",
1352 "X-Qiniu-Bxxxx: valueb\n",
1353 "X-Qiniu-Cxxxx: valuec\n",
1354 "X-Qiniu-E: value\n\n",
1355 "name=test&language=go"
1356 )
1357 .as_bytes()
1358 )
1359 );
1360 assert_eq!(
1361 credential
1362 .get(Default::default())?
1363 .authorization_v2_for_request_with_body_reader(
1364 &Method::GET,
1365 &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1366 &form_headers,
1367 &mut Cursor::new(b"name=test&language=go")
1368 )?,
1369 "Qiniu ".to_owned()
1370 + &credential.get(Default::default())?.sign(
1371 concat!(
1372 "GET /find/sdk?v=2\n",
1373 "Host: upload.qiniup.com\n",
1374 "Content-Type: application/x-www-form-urlencoded\n",
1375 "X-Qiniu-Axxxx: valuea\n",
1376 "X-Qiniu-Bxxxx: valueb\n",
1377 "X-Qiniu-Cxxxx: valuec\n",
1378 "X-Qiniu-E: value\n\n",
1379 "name=test&language=go"
1380 )
1381 .as_bytes()
1382 )
1383 );
1384 Ok(())
1385 }
1386
1387 #[test]
1388 fn test_sign_download_url() -> Result<()> {
1389 let credential = get_env_credential();
1390 let url = "http://www.qiniu.com/?go=1".parse()?;
1391 let url = credential
1392 .get(Default::default())?
1393 .sign_download_url(url, Duration::from_secs(3600));
1394 assert!(url.to_string().starts_with("http://www.qiniu.com/?go=1&e="));
1395 assert!(url.to_string().contains("&token=abcdefghklmnopq"));
1396 Ok(())
1397 }
1398
1399 #[test]
1400 fn test_chain_credentials() -> Result<()> {
1401 GlobalCredentialProvider::clear();
1402 let chain_credentials = ChainCredentialsProvider::default();
1403 env::set_var(QINIU_ACCESS_KEY_ENV_KEY, "TEST2");
1404 env::set_var(QINIU_SECRET_KEY_ENV_KEY, "test2");
1405 {
1406 let cred = chain_credentials.get(Default::default())?;
1407 assert_eq!(cred.access_key().as_str(), "TEST2");
1408 }
1409 GlobalCredentialProvider::setup(Credential::new("TEST1", "test1"));
1410 {
1411 let cred = chain_credentials.get(Default::default())?;
1412 assert_eq!(cred.access_key().as_str(), "TEST1");
1413 }
1414 Ok(())
1415 }
1416
1417 #[test]
1418 #[should_panic]
1419 fn test_build_empty_chain_credentials() {
1420 ChainCredentialsProviderBuilder::default().build();
1421 }
1422
1423 fn get_credential() -> Credential {
1424 Credential::new("abcdefghklmnopq", "1234567890")
1425 }
1426
1427 fn get_global_credential() -> GlobalCredentialProvider {
1428 GlobalCredentialProvider::setup(Credential::new("abcdefghklmnopq", "1234567890"));
1429 GlobalCredentialProvider
1430 }
1431
1432 fn get_env_credential() -> EnvCredentialProvider {
1433 env::set_var(QINIU_ACCESS_KEY_ENV_KEY, "abcdefghklmnopq");
1434 env::set_var(QINIU_SECRET_KEY_ENV_KEY, "1234567890");
1435 EnvCredentialProvider
1436 }
1437
1438 #[cfg(feature = "async")]
1439 mod async_test {
1440 use super::*;
1441 use futures_lite::io::Cursor;
1442
1443 #[async_std::test]
1444 async fn test_sign_async_reader() -> Result<()> {
1445 let credential = get_credential();
1446 assert_eq!(
1447 credential
1448 .get(Default::default())?
1449 .sign_async_reader(&mut Cursor::new(b"hello"))
1450 .await?,
1451 "abcdefghklmnopq:b84KVc-LroDiz0ebUANfdzSRxa0="
1452 );
1453 assert_eq!(
1454 credential
1455 .get(Default::default())?
1456 .sign_async_reader(&mut Cursor::new(b"world"))
1457 .await?,
1458 "abcdefghklmnopq:VjgXt0P_nCxHuaTfiFz-UjDJ1AQ="
1459 );
1460 assert_eq!(
1461 credential
1462 .get(Default::default())?
1463 .sign_async_reader(&mut Cursor::new(b"-test"))
1464 .await?,
1465 "abcdefghklmnopq:vYKRLUoXRlNHfpMEQeewG0zylaw="
1466 );
1467 assert_eq!(
1468 credential
1469 .get(Default::default())?
1470 .sign_async_reader(&mut Cursor::new(b"ba#a-"))
1471 .await?,
1472 "abcdefghklmnopq:2d_Yr6H1GdTKg3RvMtpHOhi047M="
1473 );
1474 Ok(())
1475 }
1476
1477 #[async_std::test]
1478 async fn test_async_authorization_v1() -> Result<()> {
1479 let credential = get_credential();
1480 assert_eq!(
1481 credential
1482 .get(Default::default())?
1483 .authorization_v1_for_request_with_async_body_reader(
1484 &"http://upload.qiniup.com/".parse()?,
1485 None,
1486 &mut Cursor::new(b"{\"name\":\"test\"}")
1487 )
1488 .await?,
1489 "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1490 );
1491 assert_eq!(
1492 credential
1493 .get(Default::default())?
1494 .authorization_v1_for_request_with_async_body_reader(
1495 &"http://upload.qiniup.com/".parse()?,
1496 Some(&HeaderValue::from_str(APPLICATION_JSON.as_ref())?),
1497 &mut Cursor::new(b"{\"name\":\"test\"}")
1498 )
1499 .await?,
1500 "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1501 );
1502 assert_eq!(
1503 credential
1504 .get(Default::default())?
1505 .authorization_v1_for_request_with_async_body_reader(
1506 &"http://upload.qiniup.com/".parse()?,
1507 Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1508 &mut Cursor::new(b"name=test&language=go")
1509 )
1510 .await?,
1511 "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\nname=test&language=go")
1512 );
1513 assert_eq!(
1514 credential
1515 .get(Default::default())?
1516 .authorization_v1_for_request_with_async_body_reader(
1517 &"http://upload.qiniup.com/?v=2".parse()?,
1518 Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1519 &mut Cursor::new(b"name=test&language=go")
1520 )
1521 .await?,
1522 "QBox ".to_owned()
1523 + &credential
1524 .get(Default::default())?
1525 .sign(b"/?v=2\nname=test&language=go")
1526 );
1527 assert_eq!(
1528 credential
1529 .get(Default::default())?
1530 .authorization_v1_for_request_with_async_body_reader(
1531 &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1532 Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1533 &mut Cursor::new(b"name=test&language=go")
1534 )
1535 .await?,
1536 "QBox ".to_owned()
1537 + &credential
1538 .get(Default::default())?
1539 .sign(b"/find/sdk?v=2\nname=test&language=go")
1540 );
1541 Ok(())
1542 }
1543
1544 #[async_std::test]
1545 async fn test_async_authorization_v2() -> Result<()> {
1546 let credential = get_global_credential();
1547 let empty_headers = {
1548 let mut headers = HeaderMap::new();
1549 headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1550 headers
1551 };
1552 let json_headers = {
1553 let mut headers = HeaderMap::new();
1554 headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
1555 headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1556 headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1557 headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1558 headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1559 headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1560 headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1561 headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1562 headers
1563 };
1564 let form_headers = {
1565 let mut headers = HeaderMap::new();
1566 headers.insert(
1567 CONTENT_TYPE,
1568 HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?,
1569 );
1570 headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1571 headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1572 headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1573 headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1574 headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1575 headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1576 headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1577 headers
1578 };
1579 assert_eq!(
1580 credential
1581 .get(Default::default())?
1582 .authorization_v2_for_request_with_async_body_reader(
1583 &Method::GET,
1584 &"http://upload.qiniup.com/".parse()?,
1585 &json_headers,
1586 &mut Cursor::new(b"{\"name\":\"test\"}")
1587 )
1588 .await?,
1589 "Qiniu ".to_owned()
1590 + &credential.get(Default::default())?.sign(
1591 concat!(
1592 "GET /\n",
1593 "Host: upload.qiniup.com\n",
1594 "Content-Type: application/json\n",
1595 "X-Qiniu-Axxxx: valuea\n",
1596 "X-Qiniu-Bxxxx: valueb\n",
1597 "X-Qiniu-Cxxxx: valuec\n",
1598 "X-Qiniu-E: value\n\n",
1599 "{\"name\":\"test\"}"
1600 )
1601 .as_bytes()
1602 )
1603 );
1604 assert_eq!(
1605 credential
1606 .get(Default::default())?
1607 .authorization_v2_for_request_with_async_body_reader(
1608 &Method::GET,
1609 &"http://upload.qiniup.com/".parse()?,
1610 &empty_headers,
1611 &mut Cursor::new(b"{\"name\":\"test\"}")
1612 )
1613 .await?,
1614 "Qiniu ".to_owned()
1615 + &credential
1616 .get(Default::default())?
1617 .sign(concat!("GET /\n", "Host: upload.qiniup.com\n\n").as_bytes())
1618 );
1619 assert_eq!(
1620 credential
1621 .get(Default::default())?
1622 .authorization_v2_for_request_with_async_body_reader(
1623 &Method::POST,
1624 &"http://upload.qiniup.com/".parse()?,
1625 &json_headers,
1626 &mut Cursor::new(b"{\"name\":\"test\"}")
1627 )
1628 .await?,
1629 "Qiniu ".to_owned()
1630 + &credential.get(Default::default())?.sign(
1631 concat!(
1632 "POST /\n",
1633 "Host: upload.qiniup.com\n",
1634 "Content-Type: application/json\n",
1635 "X-Qiniu-Axxxx: valuea\n",
1636 "X-Qiniu-Bxxxx: valueb\n",
1637 "X-Qiniu-Cxxxx: valuec\n",
1638 "X-Qiniu-E: value\n\n",
1639 "{\"name\":\"test\"}"
1640 )
1641 .as_bytes()
1642 )
1643 );
1644 assert_eq!(
1645 credential
1646 .get(Default::default())?
1647 .authorization_v2_for_request_with_async_body_reader(
1648 &Method::GET,
1649 &"http://upload.qiniup.com/".parse()?,
1650 &form_headers,
1651 &mut Cursor::new(b"name=test&language=go")
1652 )
1653 .await?,
1654 "Qiniu ".to_owned()
1655 + &credential.get(Default::default())?.sign(
1656 concat!(
1657 "GET /\n",
1658 "Host: upload.qiniup.com\n",
1659 "Content-Type: application/x-www-form-urlencoded\n",
1660 "X-Qiniu-Axxxx: valuea\n",
1661 "X-Qiniu-Bxxxx: valueb\n",
1662 "X-Qiniu-Cxxxx: valuec\n",
1663 "X-Qiniu-E: value\n\n",
1664 "name=test&language=go"
1665 )
1666 .as_bytes()
1667 )
1668 );
1669 assert_eq!(
1670 credential
1671 .get(Default::default())?
1672 .authorization_v2_for_request_with_async_body_reader(
1673 &Method::GET,
1674 &"http://upload.qiniup.com/?v=2".parse()?,
1675 &form_headers,
1676 &mut Cursor::new(b"name=test&language=go")
1677 )
1678 .await?,
1679 "Qiniu ".to_owned()
1680 + &credential.get(Default::default())?.sign(
1681 concat!(
1682 "GET /?v=2\n",
1683 "Host: upload.qiniup.com\n",
1684 "Content-Type: application/x-www-form-urlencoded\n",
1685 "X-Qiniu-Axxxx: valuea\n",
1686 "X-Qiniu-Bxxxx: valueb\n",
1687 "X-Qiniu-Cxxxx: valuec\n",
1688 "X-Qiniu-E: value\n\n",
1689 "name=test&language=go"
1690 )
1691 .as_bytes()
1692 )
1693 );
1694 assert_eq!(
1695 credential
1696 .get(Default::default())?
1697 .authorization_v2_for_request_with_async_body_reader(
1698 &Method::GET,
1699 &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1700 &form_headers,
1701 &mut Cursor::new(b"name=test&language=go")
1702 )
1703 .await?,
1704 "Qiniu ".to_owned()
1705 + &credential.get(Default::default())?.sign(
1706 concat!(
1707 "GET /find/sdk?v=2\n",
1708 "Host: upload.qiniup.com\n",
1709 "Content-Type: application/x-www-form-urlencoded\n",
1710 "X-Qiniu-Axxxx: valuea\n",
1711 "X-Qiniu-Bxxxx: valueb\n",
1712 "X-Qiniu-Cxxxx: valuec\n",
1713 "X-Qiniu-E: value\n\n",
1714 "name=test&language=go"
1715 )
1716 .as_bytes()
1717 )
1718 );
1719 Ok(())
1720 }
1721
1722 #[async_std::test]
1723 async fn test_async_sign_download_url() -> Result<()> {
1724 let credential = get_env_credential();
1725 let url = "http://www.qiniu.com/?go=1".parse()?;
1726 let url = credential
1727 .async_get(Default::default())
1728 .await?
1729 .sign_download_url(url, Duration::from_secs(3600));
1730 assert!(url.to_string().starts_with("http://www.qiniu.com/?go=1&e="));
1731 assert!(url.to_string().contains("&token=abcdefghklmnopq"));
1732 Ok(())
1733 }
1734
1735 #[async_std::test]
1736 async fn test_async_chain_credentials() -> Result<()> {
1737 GlobalCredentialProvider::clear();
1738 let chain_credentials = ChainCredentialsProvider::default();
1739 env::set_var(QINIU_ACCESS_KEY_ENV_KEY, "TEST2");
1740 env::set_var(QINIU_SECRET_KEY_ENV_KEY, "test2");
1741 {
1742 let cred = chain_credentials.async_get(Default::default()).await?;
1743 assert_eq!(cred.access_key().as_str(), "TEST2");
1744 }
1745 GlobalCredentialProvider::setup(Credential::new("TEST1", "test1"));
1746 {
1747 let cred = chain_credentials.async_get(Default::default()).await?;
1748 assert_eq!(cred.access_key().as_str(), "TEST1");
1749 }
1750 Ok(())
1751 }
1752 }
1753}