367 367 | expect_identity(3000, &sut, key3, || async move { panic!("new identity should not be loaded") }).await;
|
368 368 | }
|
369 369 | }
|
370 370 | }
|
371 371 | /// Supporting code for S3 Express identity provider
|
372 372 | pub(crate) mod identity_provider {
|
373 373 | use std::time::{Duration, SystemTime};
|
374 374 |
|
375 375 | use crate::s3_express::identity_cache::S3ExpressIdentityCache;
|
376 376 | use crate::types::SessionCredentials;
|
377 + | use aws_credential_types::credential_feature::AwsCredentialFeature;
|
377 378 | use aws_credential_types::provider::error::CredentialsError;
|
378 379 | use aws_credential_types::Credentials;
|
379 380 | use aws_smithy_async::time::{SharedTimeSource, TimeSource};
|
380 381 | use aws_smithy_runtime_api::box_error::BoxError;
|
381 382 | use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams;
|
382 383 | use aws_smithy_runtime_api::client::identity::{Identity, IdentityCacheLocation, IdentityFuture, ResolveCachedIdentity, ResolveIdentity};
|
383 384 | use aws_smithy_runtime_api::client::interceptors::SharedInterceptor;
|
384 385 | use aws_smithy_runtime_api::client::runtime_components::{GetIdentityResolver, RuntimeComponents};
|
385 386 | use aws_smithy_runtime_api::shared::IntoShared;
|
386 387 | use aws_smithy_types::config_bag::ConfigBag;
|
387 388 |
|
388 389 | use super::identity_cache::{DEFAULT_BUFFER_TIME, DEFAULT_MAX_CACHE_CAPACITY};
|
389 390 |
|
390 391 | #[derive(Debug)]
|
391 392 | pub(crate) struct DefaultS3ExpressIdentityProvider {
|
392 393 | behavior_version: crate::config::BehaviorVersion,
|
393 394 | cache: S3ExpressIdentityCache,
|
394 395 | }
|
395 396 |
|
396 397 | impl TryFrom<SessionCredentials> for Credentials {
|
397 398 | type Error = BoxError;
|
398 399 |
|
399 400 | fn try_from(session_creds: SessionCredentials) -> Result<Self, Self::Error> {
|
400 401 | Ok(Credentials::new(
|
401 402 | session_creds.access_key_id,
|
402 403 | session_creds.secret_access_key,
|
403 404 | Some(session_creds.session_token),
|
404 405 | Some(
|
405 406 | SystemTime::try_from(session_creds.expiration)
|
406 407 | .map_err(|_| CredentialsError::unhandled("credential expiration time cannot be represented by a SystemTime"))?,
|
407 408 | ),
|
408 409 | "s3express",
|
409 410 | ))
|
410 411 | }
|
411 412 | }
|
412 413 |
|
413 414 | impl DefaultS3ExpressIdentityProvider {
|
414 415 | pub(crate) fn builder() -> Builder {
|
415 416 | Builder::default()
|
416 417 | }
|
417 418 |
|
418 419 | async fn identity<'a>(&'a self, runtime_components: &'a RuntimeComponents, config_bag: &'a ConfigBag) -> Result<Identity, BoxError> {
|
419 420 | let bucket_name = self.bucket_name(config_bag)?;
|
420 421 |
|
421 422 | let sigv4_identity_resolver = runtime_components
|
422 423 | .identity_resolver(aws_runtime::auth::sigv4::SCHEME_ID)
|
423 424 | .ok_or("identity resolver for sigv4 should be set for S3")?;
|
424 425 | let aws_identity = runtime_components
|
425 426 | .identity_cache()
|
426 427 | .resolve_cached_identity(sigv4_identity_resolver, runtime_components, config_bag)
|
427 428 | .await?;
|
428 429 |
|
429 430 | let credentials = aws_identity
|
430 431 | .data::<Credentials>()
|
431 432 | .ok_or("wrong identity type for SigV4. Expected AWS credentials but got `{identity:?}")?;
|
432 433 |
|
433 434 | let key = self.cache.key(bucket_name, credentials);
|
434 435 | self.cache
|
435 436 | .get_or_load(key, || async move {
|
436 437 | let creds = self.express_session_credentials(bucket_name, runtime_components, config_bag).await?;
|
437 - | let data = Credentials::try_from(creds)?;
|
438 + | let mut data = Credentials::try_from(creds)?;
|
439 + | data.get_property_mut_or_default::<Vec<AwsCredentialFeature>>()
|
440 + | .push(AwsCredentialFeature::S3ExpressBucket);
|
438 441 | Ok((Identity::new(data.clone(), data.expiry()), data.expiry().unwrap()))
|
439 442 | })
|
440 443 | .await
|
441 444 | }
|
442 445 |
|
443 446 | fn bucket_name<'a>(&'a self, config_bag: &'a ConfigBag) -> Result<&'a str, BoxError> {
|
444 447 | let params = config_bag.load::<EndpointResolverParams>().expect("endpoint resolver params must be set");
|
445 448 | let params = params
|
446 449 | .get::<crate::config::endpoint::Params>()
|
447 450 | .expect("`Params` should be wrapped in `EndpointResolverParams`");
|
517 520 |
|
518 521 | impl ResolveIdentity for DefaultS3ExpressIdentityProvider {
|
519 522 | fn resolve_identity<'a>(&'a self, runtime_components: &'a RuntimeComponents, config_bag: &'a ConfigBag) -> IdentityFuture<'a> {
|
520 523 | IdentityFuture::new(async move { self.identity(runtime_components, config_bag).await })
|
521 524 | }
|
522 525 |
|
523 526 | fn cache_location(&self) -> IdentityCacheLocation {
|
524 527 | IdentityCacheLocation::IdentityResolver
|
525 528 | }
|
526 529 | }
|
530 + |
|
531 + | #[cfg(test)]
|
532 + | mod tests {
|
533 + | use super::*;
|
534 + | use aws_credential_types::credential_feature::AwsCredentialFeature;
|
535 + | use aws_credential_types::Credentials;
|
536 + |
|
537 + | // Helper function to create test runtime components with SigV4 identity resolver
|
538 + | fn create_test_runtime_components(
|
539 + | base_credentials: Credentials,
|
540 + | http_client: impl aws_smithy_runtime_api::client::http::HttpClient + 'static,
|
541 + | ) -> aws_smithy_runtime_api::client::runtime_components::RuntimeComponents {
|
542 + | use aws_credential_types::provider::SharedCredentialsProvider;
|
543 + | use aws_smithy_runtime::client::retries::strategy::NeverRetryStrategy;
|
544 + | use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolver;
|
545 + | use aws_smithy_runtime_api::client::endpoint::SharedEndpointResolver;
|
546 + | use aws_smithy_runtime_api::client::identity::SharedIdentityResolver;
|
547 + | use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
|
548 + |
|
549 + | let sigv4_resolver = SharedIdentityResolver::new(SharedCredentialsProvider::new(base_credentials));
|
550 + |
|
551 + | // Create a simple auth scheme option resolver for testing
|
552 + | let auth_option_resolver = StaticAuthSchemeOptionResolver::new(vec![aws_runtime::auth::sigv4::SCHEME_ID]);
|
553 + |
|
554 + | // Create a test endpoint resolver
|
555 + | #[derive(Debug)]
|
556 + | struct TestEndpointResolver;
|
557 + | impl aws_smithy_runtime_api::client::endpoint::ResolveEndpoint for TestEndpointResolver {
|
558 + | fn resolve_endpoint<'a>(
|
559 + | &'a self,
|
560 + | _params: &'a aws_smithy_runtime_api::client::endpoint::EndpointResolverParams,
|
561 + | ) -> aws_smithy_runtime_api::client::endpoint::EndpointFuture<'a> {
|
562 + | aws_smithy_runtime_api::client::endpoint::EndpointFuture::ready(Ok(aws_smithy_types::endpoint::Endpoint::builder()
|
563 + | .url("https://test-bucket--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com")
|
564 + | .build()))
|
565 + | }
|
566 + | }
|
567 + |
|
568 + | RuntimeComponentsBuilder::for_tests()
|
569 + | .with_identity_resolver(aws_runtime::auth::sigv4::SCHEME_ID, sigv4_resolver)
|
570 + | .with_http_client(Some(http_client))
|
571 + | .with_time_source(Some(aws_smithy_async::time::SystemTimeSource::new()))
|
572 + | .with_retry_strategy(Some(NeverRetryStrategy::new()))
|
573 + | .with_auth_scheme_option_resolver(Some(auth_option_resolver))
|
574 + | .with_endpoint_resolver(Some(SharedEndpointResolver::new(TestEndpointResolver)))
|
575 + | .build()
|
576 + | .unwrap()
|
577 + | }
|
578 + |
|
579 + | // Helper function to create config bag with S3 Express bucket endpoint parameters
|
580 + | fn create_test_config_bag(bucket_name: &str) -> aws_smithy_types::config_bag::ConfigBag {
|
581 + | use aws_runtime::auth::SigV4OperationSigningConfig;
|
582 + | use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams;
|
583 + | use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
|
584 + | use aws_smithy_types::config_bag::{ConfigBag, Layer};
|
585 + | use aws_smithy_types::endpoint::Endpoint;
|
586 + | use aws_types::region::{Region, SigningRegion};
|
587 + | use aws_types::SigningName;
|
588 + |
|
589 + | let mut config_bag = ConfigBag::base();
|
590 + | let mut layer = Layer::new("test");
|
591 + |
|
592 + | let endpoint_params = EndpointResolverParams::new(crate::config::endpoint::Params::builder().bucket(bucket_name).build().unwrap());
|
593 + | layer.store_put(endpoint_params);
|
594 + |
|
595 + | // Add a test endpoint
|
596 + | let endpoint = Endpoint::builder()
|
597 + | .url(format!("https://{}.s3express-usw2-az1.us-west-2.amazonaws.com", bucket_name))
|
598 + | .build();
|
599 + | layer.store_put(endpoint);
|
600 + |
|
601 + | // Add stalled stream protection config
|
602 + | layer.store_put(StalledStreamProtectionConfig::disabled());
|
603 + |
|
604 + | // Add region for the S3 client config
|
605 + | layer.store_put(crate::config::Region::new("us-west-2"));
|
606 + |
|
607 + | // Add SigV4 operation signing config
|
608 + | let signing_config = SigV4OperationSigningConfig {
|
609 + | region: Some(SigningRegion::from(Region::new("us-west-2"))),
|
610 + | name: Some(SigningName::from_static("s3")),
|
611 + | ..Default::default()
|
612 + | };
|
613 + | layer.store_put(signing_config);
|
614 + |
|
615 + | config_bag.push_layer(layer);
|
616 + |
|
617 + | config_bag
|
618 + | }
|
619 + |
|
620 + | // Helper function to create mocked HTTP client for create_session responses
|
621 + | fn create_mock_http_client(_bucket_name: &str) -> impl aws_smithy_runtime_api::client::http::HttpClient {
|
622 + | use aws_smithy_runtime_api::client::http::{HttpClient, HttpConnector, HttpConnectorFuture, SharedHttpConnector};
|
623 + | use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse};
|
624 + | use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
|
625 + | use aws_smithy_types::body::SdkBody;
|
626 + |
|
627 + | #[derive(Debug)]
|
628 + | struct TestHttpClient;
|
629 + |
|
630 + | impl HttpClient for TestHttpClient {
|
631 + | fn http_connector(
|
632 + | &self,
|
633 + | _settings: &aws_smithy_runtime_api::client::http::HttpConnectorSettings,
|
634 + | _components: &RuntimeComponents,
|
635 + | ) -> SharedHttpConnector {
|
636 + | #[derive(Debug)]
|
637 + | struct TestConnector;
|
638 + |
|
639 + | impl HttpConnector for TestConnector {
|
640 + | fn call(&self, _request: HttpRequest) -> HttpConnectorFuture {
|
641 + | let response = http::Response::builder()
|
642 + | .status(200)
|
643 + | .body(SdkBody::from(
|
644 + | r#"<?xml version="1.0" encoding="UTF-8"?>
|
645 + | <CreateSessionResult>
|
646 + | <Credentials>
|
647 + | <AccessKeyId>session_access_key</AccessKeyId>
|
648 + | <SecretAccessKey>session_secret_key</SecretAccessKey>
|
649 + | <SessionToken>session_token</SessionToken>
|
650 + | <Expiration>2025-01-01T00:00:00Z</Expiration>
|
651 + | </Credentials>
|
652 + | </CreateSessionResult>"#,
|
653 + | ))
|
654 + | .unwrap();
|
655 + |
|
656 + | HttpConnectorFuture::ready(Ok(HttpResponse::try_from(response).unwrap()))
|
657 + | }
|
658 + | }
|
659 + |
|
660 + | SharedHttpConnector::new(TestConnector)
|
661 + | }
|
662 + | }
|
663 + |
|
664 + | TestHttpClient
|
665 + | }
|
666 + |
|
667 + | #[test]
|
668 + | fn test_session_credentials_conversion() {
|
669 + | let session_creds = SessionCredentials::builder()
|
670 + | .access_key_id("test_access_key")
|
671 + | .secret_access_key("test_secret_key")
|
672 + | .session_token("test_session_token")
|
673 + | .expiration(aws_smithy_types::DateTime::from_secs(1000))
|
674 + | .build()
|
675 + | .expect("valid session credentials");
|
676 + |
|
677 + | let credentials = Credentials::try_from(session_creds).expect("conversion should succeed");
|
678 + |
|
679 + | assert_eq!(credentials.access_key_id(), "test_access_key");
|
680 + | assert_eq!(credentials.secret_access_key(), "test_secret_key");
|
681 + | assert_eq!(credentials.session_token(), Some("test_session_token"));
|
682 + | }
|
683 + |
|
684 + | #[tokio::test]
|
685 + | async fn test_identity_provider_embeds_s3express_feature() {
|
686 + | let bucket_name = "test-bucket--usw2-az1--x-s3";
|
687 + |
|
688 + | // Use helper functions to set up test components
|
689 + | let base_credentials = Credentials::for_tests();
|
690 + | let http_client = create_mock_http_client(bucket_name);
|
691 + | let runtime_components = create_test_runtime_components(base_credentials, http_client);
|
692 + | let config_bag = create_test_config_bag(bucket_name);
|
693 + |
|
694 + | // Create the identity provider
|
695 + | let provider = DefaultS3ExpressIdentityProvider::builder()
|
696 + | .behavior_version(crate::config::BehaviorVersion::latest())
|
697 + | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
698 + | .build();
|
699 + |
|
700 + | // Call identity() and verify the S3ExpressBucket feature is present
|
701 + | let identity = provider
|
702 + | .identity(&runtime_components, &config_bag)
|
703 + | .await
|
704 + | .expect("identity() should succeed");
|
705 + |
|
706 + | let credentials = identity.data::<Credentials>().expect("Identity should contain Credentials");
|
707 + |
|
708 + | let features = credentials
|
709 + | .get_property::<Vec<AwsCredentialFeature>>()
|
710 + | .expect("Credentials should have features");
|
711 + |
|
712 + | assert!(
|
713 + | features.contains(&AwsCredentialFeature::S3ExpressBucket),
|
714 + | "S3ExpressBucket feature should be present in credentials returned by identity()"
|
715 + | );
|
716 + | }
|
717 + |
|
718 + | #[tokio::test]
|
719 + | async fn test_default_provider_resolves_identity() {
|
720 + | let bucket_name = "test-bucket--usw2-az1--x-s3";
|
721 + |
|
722 + | // Use helper functions to set up test components
|
723 + | let base_credentials = Credentials::for_tests();
|
724 + | let http_client = create_mock_http_client(bucket_name);
|
725 + | let runtime_components = create_test_runtime_components(base_credentials, http_client);
|
726 + | let config_bag = create_test_config_bag(bucket_name);
|
727 + |
|
728 + | // Use builder pattern to construct DefaultS3ExpressIdentityProvider
|
729 + | let provider = DefaultS3ExpressIdentityProvider::builder()
|
730 + | .behavior_version(crate::config::BehaviorVersion::latest())
|
731 + | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
732 + | .build();
|
733 + |
|
734 + | // Call identity() method and verify it succeeds
|
735 + | let identity = provider
|
736 + | .identity(&runtime_components, &config_bag)
|
737 + | .await
|
738 + | .expect("identity() should succeed");
|
739 + |
|
740 + | // Verify the identity contains credentials
|
741 + | assert!(identity.data::<Credentials>().is_some(), "Identity should contain Credentials");
|
742 + | }
|
743 + |
|
744 + | #[tokio::test]
|
745 + | async fn test_error_missing_sigv4_identity_resolver() {
|
746 + | let bucket_name = "test-bucket--usw2-az1--x-s3";
|
747 + |
|
748 + | // Create runtime components WITHOUT SigV4 identity resolver
|
749 + | use aws_smithy_runtime::client::retries::strategy::NeverRetryStrategy;
|
750 + | use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolver;
|
751 + | use aws_smithy_runtime_api::client::endpoint::SharedEndpointResolver;
|
752 + | use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
|
753 + |
|
754 + | #[derive(Debug)]
|
755 + | struct TestEndpointResolver;
|
756 + | impl aws_smithy_runtime_api::client::endpoint::ResolveEndpoint for TestEndpointResolver {
|
757 + | fn resolve_endpoint<'a>(
|
758 + | &'a self,
|
759 + | _params: &'a aws_smithy_runtime_api::client::endpoint::EndpointResolverParams,
|
760 + | ) -> aws_smithy_runtime_api::client::endpoint::EndpointFuture<'a> {
|
761 + | aws_smithy_runtime_api::client::endpoint::EndpointFuture::ready(Ok(aws_smithy_types::endpoint::Endpoint::builder()
|
762 + | .url("https://test-bucket--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com")
|
763 + | .build()))
|
764 + | }
|
765 + | }
|
766 + |
|
767 + | let http_client = create_mock_http_client(bucket_name);
|
768 + | let auth_option_resolver = StaticAuthSchemeOptionResolver::new(vec![aws_runtime::auth::sigv4::SCHEME_ID]);
|
769 + |
|
770 + | // Build runtime components without SigV4 identity resolver
|
771 + | let runtime_components = RuntimeComponentsBuilder::for_tests()
|
772 + | .with_http_client(Some(http_client))
|
773 + | .with_time_source(Some(aws_smithy_async::time::SystemTimeSource::new()))
|
774 + | .with_retry_strategy(Some(NeverRetryStrategy::new()))
|
775 + | .with_auth_scheme_option_resolver(Some(auth_option_resolver))
|
776 + | .with_endpoint_resolver(Some(SharedEndpointResolver::new(TestEndpointResolver)))
|
777 + | .build()
|
778 + | .unwrap();
|
779 + |
|
780 + | let config_bag = create_test_config_bag(bucket_name);
|
781 + |
|
782 + | // Create provider
|
783 + | let provider = DefaultS3ExpressIdentityProvider::builder()
|
784 + | .behavior_version(crate::config::BehaviorVersion::latest())
|
785 + | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
786 + | .build();
|
787 + |
|
788 + | // Call identity() and verify it fails with appropriate error message
|
789 + | let result = provider.identity(&runtime_components, &config_bag).await;
|
790 + |
|
791 + | assert!(result.is_err(), "identity() should fail when SigV4 identity resolver is missing");
|
792 + | let error_message = result.unwrap_err().to_string();
|
793 + | assert!(
|
794 + | error_message.contains("identity resolver for sigv4 should be set for S3"),
|
795 + | "Error message should indicate missing SigV4 identity resolver, got: {}",
|
796 + | error_message
|
797 + | );
|
798 + | }
|
799 + |
|
800 + | #[tokio::test]
|
801 + | #[should_panic(expected = "endpoint resolver params must be set")]
|
802 + | async fn test_error_missing_endpoint_parameters() {
|
803 + | let bucket_name = "test-bucket--usw2-az1--x-s3";
|
804 + |
|
805 + | // Create runtime components with SigV4 identity resolver
|
806 + | let base_credentials = Credentials::for_tests();
|
807 + | let http_client = create_mock_http_client(bucket_name);
|
808 + | let runtime_components = create_test_runtime_components(base_credentials, http_client);
|
809 + |
|
810 + | // Create config bag WITHOUT endpoint parameters
|
811 + | use aws_smithy_types::config_bag::ConfigBag;
|
812 + | let config_bag = ConfigBag::base();
|
813 + |
|
814 + | // Create provider
|
815 + | let provider = DefaultS3ExpressIdentityProvider::builder()
|
816 + | .behavior_version(crate::config::BehaviorVersion::latest())
|
817 + | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
818 + | .build();
|
819 + |
|
820 + | // Call identity() - this should panic with the expected message
|
821 + | let _ = provider.identity(&runtime_components, &config_bag).await;
|
822 + | }
|
823 + |
|
824 + | #[tokio::test]
|
825 + | async fn test_error_missing_bucket_name_in_endpoint_params() {
|
826 + | let bucket_name = "test-bucket--usw2-az1--x-s3";
|
827 + |
|
828 + | // Create runtime components with SigV4 identity resolver
|
829 + | let base_credentials = Credentials::for_tests();
|
830 + | let http_client = create_mock_http_client(bucket_name);
|
831 + | let runtime_components = create_test_runtime_components(base_credentials, http_client);
|
832 + |
|
833 + | // Create config bag with endpoint parameters but WITHOUT bucket name
|
834 + | use aws_runtime::auth::SigV4OperationSigningConfig;
|
835 + | use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams;
|
836 + | use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
|
837 + | use aws_smithy_types::config_bag::{ConfigBag, Layer};
|
838 + | use aws_smithy_types::endpoint::Endpoint;
|
839 + | use aws_types::region::{Region, SigningRegion};
|
840 + | use aws_types::SigningName;
|
841 + |
|
842 + | let mut config_bag = ConfigBag::base();
|
843 + | let mut layer = Layer::new("test");
|
844 + |
|
845 + | // Create endpoint params without bucket name
|
846 + | let endpoint_params = EndpointResolverParams::new(
|
847 + | crate::config::endpoint::Params::builder()
|
848 + | // Intentionally NOT setting bucket name
|
849 + | .build()
|
850 + | .unwrap(),
|
851 + | );
|
852 + | layer.store_put(endpoint_params);
|
853 + |
|
854 + | // Add other required config
|
855 + | let endpoint = Endpoint::builder().url("https://s3.us-west-2.amazonaws.com").build();
|
856 + | layer.store_put(endpoint);
|
857 + | layer.store_put(StalledStreamProtectionConfig::disabled());
|
858 + | layer.store_put(crate::config::Region::new("us-west-2"));
|
859 + |
|
860 + | let signing_config = SigV4OperationSigningConfig {
|
861 + | region: Some(SigningRegion::from(Region::new("us-west-2"))),
|
862 + | name: Some(SigningName::from_static("s3")),
|
863 + | ..Default::default()
|
864 + | };
|
865 + | layer.store_put(signing_config);
|
866 + |
|
867 + | config_bag.push_layer(layer);
|
868 + |
|
869 + | // Create provider
|
870 + | let provider = DefaultS3ExpressIdentityProvider::builder()
|
871 + | .behavior_version(crate::config::BehaviorVersion::latest())
|
872 + | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
873 + | .build();
|
874 + |
|
875 + | // Call identity() and verify it fails with appropriate error message
|
876 + | let result = provider.identity(&runtime_components, &config_bag).await;
|
877 + |
|
878 + | assert!(result.is_err(), "identity() should fail when bucket name is missing from endpoint params");
|
879 + | let error_message = result.unwrap_err().to_string();
|
880 + | assert!(
|
881 + | error_message.contains("A bucket was not set in endpoint params"),
|
882 + | "Error message should indicate missing bucket name, got: {}",
|
883 + | error_message
|
884 + | );
|
885 + | }
|
886 + | }
|
527 887 | }
|
528 888 |
|
529 889 | /// Supporting code for S3 Express runtime plugin
|
530 890 | pub(crate) mod runtime_plugin {
|
531 891 | use std::borrow::Cow;
|
532 892 |
|
533 893 | use aws_runtime::auth::SigV4SessionTokenNameOverride;
|
534 894 | use aws_sigv4::http_request::{SignatureLocation, SigningSettings};
|
535 895 | use aws_smithy_runtime_api::{
|
536 896 | box_error::BoxError,
|
644 1004 | mod tests {
|
645 1005 | use super::*;
|
646 1006 | use aws_credential_types::Credentials;
|
647 1007 | use aws_smithy_runtime_api::client::identity::ResolveIdentity;
|
648 1008 |
|
649 1009 | #[test]
|
650 1010 | fn disable_option_set_from_service_client_should_take_the_highest_precedence() {
|
651 1011 | // Disable option is set from service client.
|
652 1012 | let disable_s3_express_session_token = crate::config::DisableS3ExpressSessionAuth(true);
|
653 1013 |
|
654 - | // An environment variable says the session auth is _not_ disabled, but it will be
|
655 - | // overruled by what is in `layer`.
|
1014 + | // An environment variable says the session auth is _not_ disabled,
|
1015 + | // but it will be overruled by what is in `layer`.
|
656 1016 | let actual = config(
|
657 1017 | Some(disable_s3_express_session_token),
|
658 1018 | Env::from_slice(&[(super::env::S3_DISABLE_EXPRESS_SESSION_AUTH, "false")]),
|
659 1019 | );
|
660 1020 |
|
661 - | // A config layer from this runtime plugin should not provide a new `DisableS3ExpressSessionAuth`
|
662 - | // if the disable option is set from service client.
|
1021 + | // A config layer from this runtime plugin should not provide
|
1022 + | // a new `DisableS3ExpressSessionAuth` if the disable option is set from service client.
|
663 1023 | assert!(actual.load::<crate::config::DisableS3ExpressSessionAuth>().is_none());
|
664 1024 | }
|
665 1025 |
|
666 1026 | #[test]
|
667 1027 | fn disable_option_set_from_env_should_take_the_second_highest_precedence() {
|
668 - | // An environment variable says session auth is disabled
|
1028 + | // Disable option is set from environment variable.
|
669 1029 | let actual = config(None, Env::from_slice(&[(super::env::S3_DISABLE_EXPRESS_SESSION_AUTH, "true")]));
|
670 1030 |
|
1031 + | // The config layer should provide `DisableS3ExpressSessionAuth` from the environment variable.
|
671 1032 | assert!(actual.load::<crate::config::DisableS3ExpressSessionAuth>().unwrap().0);
|
672 1033 | }
|
673 1034 |
|
674 1035 | #[should_panic]
|
675 1036 | #[test]
|
676 1037 | fn disable_option_set_from_profile_file_should_take_the_lowest_precedence() {
|
677 - | // TODO(aws-sdk-rust#1073): Implement a test that mimics only setting
|
678 - | // `s3_disable_express_session_auth` in a profile file
|
679 - | todo!()
|
1038 + | todo!("TODO(aws-sdk-rust#1073): Implement profile file test")
|
680 1039 | }
|
681 1040 |
|
682 1041 | #[test]
|
683 1042 | fn disable_option_should_be_unspecified_if_unset() {
|
1043 + | // Disable option is not set anywhere.
|
684 1044 | let actual = config(None, Env::from_slice(&[]));
|
685 1045 |
|
1046 + | // The config layer should not provide `DisableS3ExpressSessionAuth` when it's not configured.
|
686 1047 | assert!(actual.load::<crate::config::DisableS3ExpressSessionAuth>().is_none());
|
687 1048 | }
|
688 1049 |
|
689 1050 | #[test]
|
690 1051 | fn s3_express_runtime_plugin_should_set_default_identity_resolver() {
|
1052 + | // Config has SigV4 credentials provider, so S3 Express identity resolver should be set.
|
691 1053 | let config = crate::Config::builder()
|
692 1054 | .behavior_version_latest()
|
693 1055 | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
694 1056 | .credentials_provider(Credentials::for_tests())
|
695 1057 | .build();
|
696 1058 |
|
697 1059 | let actual = runtime_components_builder(config);
|
1060 + | // The runtime plugin should provide a default S3 Express identity resolver.
|
698 1061 | assert!(actual.identity_resolver(&crate::s3_express::auth::SCHEME_ID).is_some());
|
699 1062 | }
|
700 1063 |
|
701 1064 | #[test]
|
702 1065 | fn s3_express_plugin_should_not_set_default_identity_resolver_without_sigv4_counterpart() {
|
1066 + | // Config does not have SigV4 credentials provider.
|
703 1067 | let config = crate::Config::builder()
|
704 1068 | .behavior_version_latest()
|
705 1069 | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
706 1070 | .build();
|
707 1071 |
|
708 1072 | let actual = runtime_components_builder(config);
|
1073 + | // The runtime plugin should not provide S3 Express identity resolver without SigV4 credentials.
|
709 1074 | assert!(actual.identity_resolver(&crate::s3_express::auth::SCHEME_ID).is_none());
|
710 1075 | }
|
711 1076 |
|
712 1077 | #[tokio::test]
|
713 1078 | async fn s3_express_plugin_should_not_set_default_identity_resolver_if_user_provided() {
|
1079 + | // User provides a custom S3 Express credentials provider.
|
714 1080 | let expected_access_key_id = "expected acccess key ID";
|
715 1081 | let config = crate::Config::builder()
|
716 1082 | .behavior_version_latest()
|
717 1083 | .credentials_provider(Credentials::for_tests())
|
718 1084 | .express_credentials_provider(Credentials::new(
|
719 1085 | expected_access_key_id,
|
720 1086 | "secret",
|
721 1087 | None,
|
722 1088 | None,
|
723 1089 | "test express credentials provider",
|
724 1090 | ))
|
725 1091 | .time_source(aws_smithy_async::time::SystemTimeSource::new())
|
726 1092 | .build();
|
727 1093 |
|
728 - | // `RuntimeComponentsBuilder` from `S3ExpressRuntimePlugin` should not provide an S3Express identity resolver.
|
1094 + | // The runtime plugin should not override the user-provided identity resolver.
|
729 1095 | let runtime_components_builder = runtime_components_builder(config.clone());
|
730 1096 | assert!(runtime_components_builder
|
731 1097 | .identity_resolver(&crate::s3_express::auth::SCHEME_ID)
|
732 1098 | .is_none());
|
733 1099 |
|
734 - | // Get the S3Express identity resolver from the service config.
|
1100 + | // The user-provided identity resolver should be used.
|
735 1101 | let express_identity_resolver = config.runtime_components.identity_resolver(&crate::s3_express::auth::SCHEME_ID).unwrap();
|
736 1102 | let creds = express_identity_resolver
|
737 1103 | .resolve_identity(&RuntimeComponentsBuilder::for_tests().build().unwrap(), &ConfigBag::base())
|
738 1104 | .await
|
739 1105 | .unwrap();
|
740 1106 |
|
741 - | // Verify credentials are the one generated by the S3Express identity resolver user provided.
|
742 1107 | assert_eq!(expected_access_key_id, creds.data::<Credentials>().unwrap().access_key_id());
|
743 1108 | }
|
744 1109 | }
|
745 1110 | }
|
746 1111 |
|
747 1112 | pub(crate) mod checksum {
|
748 1113 | use crate::http_request_checksum::DefaultRequestChecksumOverride;
|
749 1114 | use aws_smithy_checksums::ChecksumAlgorithm;
|
750 1115 | use aws_smithy_types::config_bag::ConfigBag;
|
751 1116 |
|