From 842ee41feb77162624d8c66ff5d76fa97d67b70a Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 9 Jul 2025 16:16:43 -0500 Subject: [PATCH 01/10] Add ability to map boxed future in `new_type_future` --- rust-runtime/aws-smithy-async/Cargo.toml | 2 +- .../src/future/now_or_later.rs | 74 ++++++++++ .../aws-smithy-runtime-api/Cargo.toml | 2 +- .../aws-smithy-runtime-api/src/client/auth.rs | 126 +++++++++++++++++- 4 files changed, 201 insertions(+), 3 deletions(-) diff --git a/rust-runtime/aws-smithy-async/Cargo.toml b/rust-runtime/aws-smithy-async/Cargo.toml index c2cde40d505..9462e013af6 100644 --- a/rust-runtime/aws-smithy-async/Cargo.toml +++ b/rust-runtime/aws-smithy-async/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-async" -version = "1.2.5" +version = "1.3.0" authors = ["AWS Rust SDK Team ", "John DiSanti "] description = "Async runtime agnostic abstractions for smithy-rs." edition = "2021" diff --git a/rust-runtime/aws-smithy-async/src/future/now_or_later.rs b/rust-runtime/aws-smithy-async/src/future/now_or_later.rs index a30e9435534..231aa693e59 100644 --- a/rust-runtime/aws-smithy-async/src/future/now_or_later.rs +++ b/rust-runtime/aws-smithy-async/src/future/now_or_later.rs @@ -125,6 +125,66 @@ impl NowOrLater { } } +impl<'a, T, F> NowOrLater +where + F: Future + Send + 'a, +{ + /// Maps the value inside `NowOrLater` and returns a boxed future with lifetime `'a`. + /// + /// Examples + /// + /// ```no_run + /// # use aws_smithy_async::future::now_or_later::{NowOrLater, BoxFuture}; + /// # async fn map_boxed_later_variant() { + /// let later = NowOrLater::new(async { 10 }); + /// let mapped = later.map_boxed(|x| x + 5); + /// assert_eq!(15, mapped.await); + /// # } + /// ``` + pub fn map_boxed(self, map_fn: M) -> NowOrLater> + where + M: FnOnce(T) -> U + Send + 'a, + { + match self.inner { + Inner::Now { value } => { + let mapped = value.map(map_fn); + NowOrLater { + inner: Inner::Now { value: mapped }, + } + } + Inner::Later { future } => { + let fut = async move { + let val = future.await; + map_fn(val) + }; + NowOrLater { + inner: Inner::Later { + future: Box::pin(fut), + }, + } + } + } + } +} + +impl NowOrLater { + /// Like [`NowOrLater::map_boxed`], but specialized for use with [`OnlyReady`] + pub fn map_boxed(self, map_fn: M) -> NowOrLater + where + M: FnOnce(T) -> U, + { + match self.inner { + Inner::Now { value } => { + let mapped = value.map(map_fn); + NowOrLater { + inner: Inner::Now { value: mapped }, + } + } + Inner::Later { .. } => unreachable!(), + } + } +} + impl Future for NowOrLater where F: Future, @@ -194,4 +254,18 @@ mod test { let wrapped = NowOrLater::new(async { 5 }); assert_eq!(wrapped.await, 5); } + + #[tokio::test] + async fn map_boxed_now_variant() { + let now: NowOrLater = NowOrLater::ready(21); + let mapped = now.map_boxed(|x| x * 2); + assert_eq!(42, mapped.await); + } + + #[tokio::test] + async fn map_boxed_later_variant() { + let later = NowOrLater::new(async { 10 }); + let mapped = later.map_boxed(|x| x + 5); + assert_eq!(15, mapped.await); + } } diff --git a/rust-runtime/aws-smithy-runtime-api/Cargo.toml b/rust-runtime/aws-smithy-runtime-api/Cargo.toml index bebdc29dfa9..86d83300ccf 100644 --- a/rust-runtime/aws-smithy-runtime-api/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-runtime-api" -version = "1.8.3" +version = "1.8.4" authors = ["AWS Rust SDK Team ", "Zelda Hessler "] description = "Smithy runtime types." edition = "2021" diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs b/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs index 183d36602ec..77765affb79 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs @@ -15,7 +15,7 @@ use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Storable, StoreReplac use aws_smithy_types::type_erasure::TypeErasedBox; use aws_smithy_types::Document; use std::borrow::Cow; -use std::fmt; +use std::fmt::{self, Debug}; use std::sync::Arc; /// Auth schemes for the HTTP `Authorization` header. @@ -225,6 +225,25 @@ new_type_future! { pub struct AuthSchemeOptionsFuture<'a, Vec, BoxError>; } +// Currently, we don't add `map_ok` to the `new_type_future` macro in general. +// It's specifically used for `AuthSchemeOptionsFuture`, but we can expand it later if needed. +impl<'a> AuthSchemeOptionsFuture<'a> { + /// Transforms the `Ok` variant inside this `AuthSchemeOptionsFuture` by applying the provided function. + /// + /// This method maps over the `Ok` variant of the `Result` wrapped by the future, + /// applying `map_fn` to the contained `Vec`. + /// + /// The transformation is applied regardless of whether the future's value is already + /// available (`Now`) or will be computed asynchronously (`Later`). + pub fn map_ok(self, f: F) -> AuthSchemeOptionsFuture<'a> + where + F: FnOnce(Vec) -> Vec + Send + 'a, + { + let inner = self.inner.map_boxed(|result| result.map(f)); + Self { inner } + } +} + /// Resolver for auth scheme options. /// /// The orchestrator needs to select an auth scheme to sign requests with, and potentially @@ -420,3 +439,108 @@ impl<'a> From<&'a Document> for AuthSchemeEndpointConfig<'a> { Self(Some(value)) } } + +/// An ordered list of [AuthSchemeId]s +/// +/// Can be used to reorder already-resolved auth schemes by an auth scheme resolver. +/// This list is intended as a hint rather than a strict override; +/// any schemes not present in the resolved auth schemes will be ignored. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AuthSchemePreference { + preference_list: Vec, +} + +impl Storable for AuthSchemePreference { + type Storer = StoreReplace; +} + +impl IntoIterator for AuthSchemePreference { + type Item = AuthSchemeId; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.preference_list.into_iter() + } +} + +impl From for AuthSchemePreference +where + T: AsRef<[AuthSchemeId]>, +{ + fn from(slice: T) -> Self { + AuthSchemePreference { + preference_list: slice.as_ref().to_vec(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_map_ok_now_variant() { + let input = ["sigv4", "http"] + .map(|s| AuthSchemeOption::from(AuthSchemeId::from(s))) + .to_vec(); + let fut = AuthSchemeOptionsFuture::ready(Ok(input)); + + let mapped = fut.map_ok(|opts| { + opts.into_iter() + .filter(|opt| opt.scheme_id().inner() != "http") + .collect() + }); + + let result = mapped.await; + assert!(result.is_ok()); + let vec = result.unwrap(); + assert_eq!(1, vec.len()); + assert_eq!("sigv4", vec[0].scheme_id().inner()); + } + + #[tokio::test] + async fn test_map_ok_now_variant_error_no_op() { + let fut = AuthSchemeOptionsFuture::ready(Err(BoxError::from("oops"))); + + let mapped = fut.map_ok(|opts| opts); // no-op + + let result = mapped.await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "oops"); + } + + #[tokio::test] + async fn test_map_ok_later_variant() { + let input = ["foo", "bar"] + .map(|s| AuthSchemeOption::from(AuthSchemeId::from(s))) + .to_vec(); + let fut = AuthSchemeOptionsFuture::new(async move { Ok(input) }); + + let mapped = fut.map_ok(|opts| { + opts.into_iter() + .map(|mut opt| { + opt.scheme_id = + AuthSchemeId::from(Cow::Owned(opt.scheme_id().inner().to_uppercase())); + opt + }) + .collect() + }); + + let result = mapped.await; + assert!(result.is_ok()); + let vec = result.unwrap(); + assert_eq!(vec[0].scheme_id().inner(), "FOO"); + assert_eq!(vec[1].scheme_id().inner(), "BAR"); + } + + #[tokio::test] + async fn test_map_ok_later_variant_error_no_op() { + let fut = AuthSchemeOptionsFuture::new(async move { Err(BoxError::from("later fail")) }); + + let mapped = fut.map_ok(|opts| opts); // no-op + + let result = mapped.await; + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "later fail"); + } +} From eab28d4c08278be545dd5c5b765414f6d80a944d Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 9 Jul 2025 16:18:19 -0500 Subject: [PATCH 02/10] Use auth scheme preference in default auth scheme resolver --- .../auth/AuthSchemeResolverGenerator.kt | 41 +++++- .../smithy/auth/AuthTypesGeneratorTest.kt | 125 +++++++++++++++++- 2 files changed, 162 insertions(+), 4 deletions(-) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt index 5eb4b86360a..98bd4a227ec 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt @@ -54,6 +54,7 @@ class AuthSchemeResolverGenerator( .resolve("client::auth::AuthSchemeOption"), "AuthSchemeOptionResolverParams" to RuntimeType.smithyRuntimeApiClient(codegenContext.runtimeConfig).resolve("client::auth::AuthSchemeOptionResolverParams"), "AuthSchemeOptionsFuture" to RuntimeType.smithyRuntimeApiClient(runtimeConfig).resolve("client::auth::AuthSchemeOptionsFuture"), + "AuthSchemePreference" to RuntimeType.smithyRuntimeApiClient(runtimeConfig).resolve("client::auth::AuthSchemePreference"), "BoxError" to RuntimeType.boxError(runtimeConfig), "ConfigBag" to RuntimeType.configBag(codegenContext.runtimeConfig), "Debug" to RuntimeType.Debug, @@ -78,6 +79,7 @@ class AuthSchemeResolverGenerator( pub struct DefaultAuthSchemeResolver { service_defaults: Vec<#{AuthSchemeOption}>, operation_overrides: #{HashMap}<&'static str, Vec<#{AuthSchemeOption}>>, + preference: #{Option}<#{AuthSchemePreference}>, } // TODO(https://github.com/smithy-lang/smithy-rs/issues/4177): Remove `allow(...)` once the issue is addressed. @@ -90,6 +92,7 @@ class AuthSchemeResolverGenerator( Self { service_defaults: vec![#{service_defaults:W}], operation_overrides: #{operation_overrides:W}, + preference: #{None}, } } } @@ -112,7 +115,43 @@ class AuthSchemeResolverGenerator( #{additional_impl:W} - _fut + match &self.preference { + #{Some}(preference) => { + _fut.map_ok({ + // maps auth scheme ID to the index in the preference list + let preference_map: #{HashMap}<_, _> = preference + .clone() + .into_iter() + .enumerate() + .map(|(i, s)| (s, i)) + .collect(); + move |auth_scheme_options| { + let (mut preferred, non_preferred): (#{Vec}<_>, #{Vec}<_>) = auth_scheme_options + .into_iter() + .partition(|auth_scheme_option| { + preference_map.contains_key(auth_scheme_option.scheme_id()) + }); + + preferred.sort_by_key(|opt| { + preference_map + .get(opt.scheme_id()) + .expect("guaranteed by `partition`") + }); + preferred.extend(non_preferred); + preferred + } + }) + }, + #{None} => _fut, + } + } + } + + impl DefaultAuthSchemeResolver { + /// Set auth scheme preference to the default auth scheme resolver + pub fn with_auth_scheme_preference(mut self, preference: impl #{Into}<#{AuthSchemePreference}>) -> Self { + self.preference = #{Some}(preference.into()); + self } } """, diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt index a8eb9f2376d..d4d4acd4e85 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt @@ -19,7 +19,7 @@ import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest import software.amazon.smithy.rust.codegen.core.testutil.tokioTest -class AuthSchemeResolverGeneratorTest { +class AuthTypesGeneratorTest { val model = """ namespace com.test @@ -29,12 +29,13 @@ class AuthSchemeResolverGeneratorTest { @httpBearerAuth @httpApiKeyAuth(name: "X-Api-Key", in: "header") @httpBasicAuth - @auth([httpApiKeyAuth]) + @auth([httpApiKeyAuth, httpBasicAuth]) service Test { version: "1.0.0", operations: [ GetFooServiceDefault, GetFooOpOverride, + GetFooAnonymous, ] } @@ -42,6 +43,9 @@ class AuthSchemeResolverGeneratorTest { @auth([httpBasicAuth, httpBearerAuth]) operation GetFooOpOverride{} + + @auth([]) + operation GetFooAnonymous{} """.asSmithyModel(smithyVersion = "2.0") @Test @@ -96,7 +100,7 @@ class AuthSchemeResolverGeneratorTest { .iter() .map(|opt| opt.scheme_id().inner()) .collect::>(); - assert_eq!(vec!["http-api-key-auth"], actual); + assert_eq!(vec!["http-api-key-auth", "http-basic-auth"], actual); """, *codegenScope, ) @@ -131,4 +135,119 @@ class AuthSchemeResolverGeneratorTest { } project.compileAndTest() } + + @Test + fun `auth scheme preference`() { + val ctx = + testClientCodegenContext( + model, + rootDecorator = + CombinedClientCodegenDecorator( + listOf( + NoAuthDecorator(), + HttpAuthDecorator(), + ), + ), + ) + val sut = AuthTypesGenerator(ctx) + val project = TestWorkspace.testProject() + project.withModule(RustModule.private("auth_scheme_preference").cfgTest()) { + val smithyApiClient = + CargoDependency.smithyRuntimeApiClient(ctx.runtimeConfig) + .copy(features = setOf("http-auth", "test-util"), scope = DependencyScope.Dev).toType() + val codegenScope = + arrayOf( + "AuthSchemeId" to smithyApiClient.resolve("client::auth::AuthSchemeId"), + "ConfigBag" to + CargoDependency.smithyTypes(ctx.runtimeConfig) + .copy(features = setOf("test-util"), scope = DependencyScope.Dev).toType() + .resolve("config_bag::ConfigBag"), + "DefaultAuthSchemeResolver" to sut.defaultAuthSchemeResolver(), + "HTTP_API_KEY_AUTH_SCHEME_ID" to smithyApiClient.resolve("client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID"), + "HTTP_BASIC_AUTH_SCHEME_ID" to smithyApiClient.resolve("client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID"), + "HTTP_BEARER_AUTH_SCHEME_ID" to + smithyApiClient + .resolve("client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID"), + "Params" to AuthSchemeParamsGenerator(ctx).paramsStruct(), + "RuntimeComponentsBuilder" to + smithyApiClient + .resolve("client::runtime_components::RuntimeComponentsBuilder"), + "ServiceSpecificResolver" to sut.serviceSpecificResolveAuthSchemeTrait(), + ) + rustTemplate("use #{ServiceSpecificResolver};", *codegenScope) + tokioTest("test_auth_scheme_preference") { + rustTemplate( + """ + struct TestCase<'a> { + auth_scheme_params: &'a #{Params}, + preference_list: &'a[#{AuthSchemeId}], + expected_resolved_auths: Vec<#{AuthSchemeId}>, + } + + let get_foo_service_default_params = #{Params}::builder() + .operation_name("GetFooServiceDefault") + .build() + .unwrap(); + let get_foo_op_override_params = #{Params}::builder() + .operation_name("GetFooOpOverride") + .build() + .unwrap(); + let cfg = #{ConfigBag}::base(); + let rc = + #{RuntimeComponentsBuilder}::for_tests() + .build() + .unwrap(); + + for test_case in [ + TestCase { + auth_scheme_params: &get_foo_service_default_params, + preference_list: &[], + expected_resolved_auths: vec![#{HTTP_API_KEY_AUTH_SCHEME_ID}, #{HTTP_BASIC_AUTH_SCHEME_ID}], + }, + TestCase { + auth_scheme_params: &get_foo_service_default_params, + preference_list: &["bogus".into()], + expected_resolved_auths: vec![#{HTTP_API_KEY_AUTH_SCHEME_ID}, #{HTTP_BASIC_AUTH_SCHEME_ID}], + }, + TestCase { + auth_scheme_params: &get_foo_service_default_params, + preference_list: &[#{HTTP_BASIC_AUTH_SCHEME_ID}], + expected_resolved_auths: vec![#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_API_KEY_AUTH_SCHEME_ID}], + }, + TestCase { + auth_scheme_params: &get_foo_service_default_params, + preference_list: &[#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_API_KEY_AUTH_SCHEME_ID}], + expected_resolved_auths: vec![#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_API_KEY_AUTH_SCHEME_ID}], + }, + TestCase { + auth_scheme_params: &get_foo_op_override_params, + preference_list: &[#{HTTP_BEARER_AUTH_SCHEME_ID}], + expected_resolved_auths: vec![#{HTTP_BEARER_AUTH_SCHEME_ID}, #{HTTP_BASIC_AUTH_SCHEME_ID}], + }, + TestCase { + auth_scheme_params: &get_foo_op_override_params, + preference_list: &[#{HTTP_API_KEY_AUTH_SCHEME_ID}], + expected_resolved_auths: vec![#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_BEARER_AUTH_SCHEME_ID}], + }, + ].into_iter() { + let sut = #{DefaultAuthSchemeResolver}::default() + .with_auth_scheme_preference(test_case.preference_list); + let actual = sut + .resolve_auth_scheme(&test_case.auth_scheme_params, &cfg, &rc) + .await + .unwrap(); + let actual = actual + .iter() + .map(|opt| opt.scheme_id()) + .cloned() + .collect::>(); + assert_eq!(test_case.expected_resolved_auths, actual); + } + """, + *codegenScope, + ) + } + } + project.compileAndTest() + } } From 08f09d433fd7fccfcf85d3f8304e3738f1b4b7f9 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 9 Jul 2025 21:55:59 -0500 Subject: [PATCH 03/10] Make auth scheme preference configurable --- aws/rust-runtime/aws-config/Cargo.lock | 14 +- aws/rust-runtime/aws-config/Cargo.toml | 2 +- .../aws-config/external-types.toml | 11 +- .../aws-config/src/default_provider.rs | 3 + .../auth_scheme_preference.rs | 201 ++++++++++++++++++ aws/rust-runtime/aws-config/src/lib.rs | 39 +++- aws/rust-runtime/aws-types/Cargo.toml | 2 +- .../aws-types/external-types.toml | 3 +- aws/rust-runtime/aws-types/src/sdk_config.rs | 39 ++++ .../smithy/rustsdk/SdkConfigDecorator.kt | 1 + .../client/smithy/auth/AuthDecorator.kt | 99 ++++++++- 11 files changed, 386 insertions(+), 28 deletions(-) create mode 100644 aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs diff --git a/aws/rust-runtime/aws-config/Cargo.lock b/aws/rust-runtime/aws-config/Cargo.lock index cd483896c43..b30c9913db5 100644 --- a/aws/rust-runtime/aws-config/Cargo.lock +++ b/aws/rust-runtime/aws-config/Cargo.lock @@ -50,7 +50,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.8.1" +version = "1.8.3" dependencies = [ "aws-credential-types", "aws-runtime", @@ -117,7 +117,7 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.6.0" +version = "1.5.9" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -221,7 +221,7 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.5" +version = "1.3.0" dependencies = [ "futures-util", "pin-project-lite", @@ -230,7 +230,7 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.2" +version = "0.62.1" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -318,7 +318,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.3" +version = "1.8.4" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -341,7 +341,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.1" +version = "1.8.4" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -384,7 +384,7 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.7" +version = "1.3.8" dependencies = [ "aws-credential-types", "aws-smithy-async", diff --git a/aws/rust-runtime/aws-config/Cargo.toml b/aws/rust-runtime/aws-config/Cargo.toml index 17a0fa9abf3..61489eb2b64 100644 --- a/aws/rust-runtime/aws-config/Cargo.toml +++ b/aws/rust-runtime/aws-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-config" -version = "1.8.2" +version = "1.8.3" authors = [ "AWS Rust SDK Team ", "Russell Cohen ", diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index 362f2ea2921..d5ba73f03c8 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -3,10 +3,8 @@ # require manual version bumping every time an automated version bump # to the exposed SDK crates happens. allowed_external_types = [ - "aws_credential_types::cache::CredentialsCache", "aws_credential_types::provider::credentials::ProvideCredentials", "aws_credential_types::provider::credentials::Result", - "aws_credential_types::provider::credentials::SharedCredentialsProvider", "aws_credential_types::provider::token::ProvideToken", "aws_runtime::env_config::error::EnvConfigFileLoadError", "aws_runtime::env_config::file::Builder", @@ -17,30 +15,23 @@ allowed_external_types = [ "aws_runtime::env_config::section::EnvConfigSections", "aws_runtime::env_config::section::Profile", "aws_smithy_async::rt::sleep::AsyncSleep", - "aws_smithy_async::rt::sleep::SharedAsyncSleep", - "aws_smithy_async::time::SharedTimeSource", "aws_smithy_async::time::TimeSource", "aws_smithy_runtime::client::identity::cache::IdentityCache", "aws_smithy_runtime::client::identity::cache::lazy::LazyCacheBuilder", + "aws_smithy_runtime_api::client::auth::AuthSchemePreference", "aws_smithy_runtime_api::box_error::BoxError", "aws_smithy_runtime_api::client::behavior_version::BehaviorVersion", "aws_smithy_runtime_api::client::dns::ResolveDns", - "aws_smithy_runtime_api::client::dns::SharedDnsResolver", "aws_smithy_runtime_api::client::http::HttpClient", - "aws_smithy_runtime_api::client::http::SharedHttpClient", "aws_smithy_runtime_api::client::identity::ResolveCachedIdentity", "aws_smithy_runtime_api::client::identity::ResolveIdentity", "aws_smithy_runtime_api::client::orchestrator::HttpResponse", - "aws_smithy_runtime_api::client::result::SdkError", "aws_smithy_runtime_api::client::retries::classifiers::ClassifyRetry", "aws_smithy_runtime_api::client::retries::classifiers::SharedRetryClassifier", "aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig", - "aws_smithy_types::body::SdkBody", "aws_smithy_types::checksum_config::RequestChecksumCalculation", "aws_smithy_types::checksum_config::ResponseChecksumValidation", - "aws_smithy_types::retry", "aws_smithy_types::retry::*", - "aws_smithy_types::timeout", "aws_smithy_types::timeout::OperationTimeoutConfig", "aws_smithy_types::timeout::TimeoutConfig", "aws_smithy_types::timeout::TimeoutConfigBuilder", diff --git a/aws/rust-runtime/aws-config/src/default_provider.rs b/aws/rust-runtime/aws-config/src/default_provider.rs index 2687c16a40b..436778c1399 100644 --- a/aws/rust-runtime/aws-config/src/default_provider.rs +++ b/aws/rust-runtime/aws-config/src/default_provider.rs @@ -69,3 +69,6 @@ pub mod checksums; /// Default provider chain for account-based endpoint mode pub mod account_id_endpoint_mode; + +/// Default provider chain for auth scheme preference list +pub mod auth_scheme_preference; diff --git a/aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs b/aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs new file mode 100644 index 00000000000..0ff73a82842 --- /dev/null +++ b/aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs @@ -0,0 +1,201 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use crate::provider_config::ProviderConfig; +use aws_runtime::env_config::EnvConfigValue; +use aws_smithy_runtime_api::client::auth::{AuthSchemeId, AuthSchemePreference}; +use aws_smithy_types::error::display::DisplayErrorContext; +use std::borrow::Cow; +use std::fmt; + +mod env { + pub(super) const AUTH_SCHEME_PREFERENCE: &str = "AWS_AUTH_SCHEME_PREFERENCE"; +} + +mod profile_key { + pub(super) const AUTH_SCHEME_PREFERENCE: &str = "auth_scheme_preference"; +} + +/// Load the value for the auth scheme preference +/// +/// This checks the following sources: +/// 1. The environment variable `AWS_AUTH_SCHEME_PREFERENCE=scheme1,scheme2,scheme3` +/// 2. The profile key `auth_scheme_preference=scheme1,scheme2,scheme3` +/// +/// A scheme name can be either a fully qualified name or a shorthand with the namespace prefix trimmed. +/// For example, valid scheme names include "aws.auth#sigv4", "smithy.api#httpBasicAuth", "sigv4", and "httpBasicAuth". +/// Whitespace (spaces or tabs), including leading, trailing, and between names, is ignored. +/// +/// Returns `None` if a parsed string component is empty when creating an `AuthSchemeId`. +pub(crate) async fn auth_scheme_preference_provider( + provider_config: &ProviderConfig, +) -> Option { + let env = provider_config.env(); + let profiles = provider_config.profile().await; + + EnvConfigValue::new() + .env(env::AUTH_SCHEME_PREFERENCE) + .profile(profile_key::AUTH_SCHEME_PREFERENCE) + .validate(&env, profiles, parse_auth_scheme_names) + .map_err(|err| tracing::warn!(err = %DisplayErrorContext(&err), "invalid value for `AuthSchemePreference`")) + .unwrap_or(None) +} + +fn parse_auth_scheme_names(csv: &str) -> Result { + csv.split(',') + .map(|s| { + let trimmed = s.trim().replace([' ', '\t'], ""); + if trimmed.is_empty() { + return Err(InvalidAuthSchemeNamesCsv { + value: format!("Empty name found in `{csv}`."), + }); + } + let scheme_name = trimmed.split('#').next_back().unwrap_or(&trimmed); + Ok(runtime_api_auth_scheme_id(scheme_name)) + }) + .collect::, _>>() + .map(AuthSchemePreference::from) +} + +// This function is not automatically extensible. When a new scheme is introduced, +// it must be manually updated for the scheme to be recognized in the environment configuration. +// +// Furthermore, this function does not return a predefined `AuthSchemeId` like `HTTP_BASIC_AUTH_SCHEME_ID`. +// The predefined `SchemeId`s are gated behind the `http-auth` feature, but this function returns an `AuthSchemeId` +// that wraps the parsed string regardless of feature flags. If the feature is disabled, the returned +// auth scheme preference is simply ignored during auth scheme resolution. +fn runtime_api_auth_scheme_id(auth_scheme_name: &str) -> AuthSchemeId { + let runtime_api_auth_scheme_str = match auth_scheme_name { + "httpBasicAuth" => "http-basic-auth", + "httpDigestAuth" => "http-digest-auth", + "httpBearerAuth" => "http-bearer-auth", + "httpApiKeyAuth" => "http-api-key-auth", + "noAuth" => "no_auth", + otherwise => otherwise, + }; + AuthSchemeId::from(Cow::Owned(runtime_api_auth_scheme_str.to_owned())) +} + +#[derive(Debug)] +pub(crate) struct InvalidAuthSchemeNamesCsv { + value: String, +} + +impl fmt::Display for InvalidAuthSchemeNamesCsv { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Not a valid comma-separated auth scheme names: {}", + self.value + ) + } +} + +impl std::error::Error for InvalidAuthSchemeNamesCsv {} + +#[cfg(test)] +mod test { + use super::env; + use crate::{ + default_provider::auth_scheme_preference::auth_scheme_preference_provider, + provider_config::ProviderConfig, + }; + use aws_types::os_shim_internal::Env; + use tracing_test::traced_test; + + #[tokio::test] + #[traced_test] + async fn log_error_on_invalid_value() { + let conf = ProviderConfig::empty().with_env(Env::from_slice(&[( + env::AUTH_SCHEME_PREFERENCE, + "scheme1, , \tscheme2", + )])); + assert_eq!(None, auth_scheme_preference_provider(&conf).await); + assert!(logs_contain( + "Not a valid comma-separated auth scheme names: Empty name found" + )); + } + + #[cfg(feature = "sso")] // for aws-smithy-runtime-api/http-auth + mod http_auth_tests { + use super::env; + #[allow(deprecated)] + use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; + use crate::{ + default_provider::auth_scheme_preference::auth_scheme_preference_provider, + provider_config::ProviderConfig, + }; + use aws_smithy_runtime_api::client::auth::AuthSchemePreference; + use aws_types::os_shim_internal::{Env, Fs}; + + #[tokio::test] + async fn environment_priority() { + let conf = ProviderConfig::empty() + .with_env(Env::from_slice(&[( + env::AUTH_SCHEME_PREFERENCE, + "aws.auth#sigv4, smithy.api#httpBasicAuth, smithy.api#httpDigestAuth, smithy.api#httpBearerAuth, smithy.api#httpApiKeyAuth", + )])) + .with_profile_config( + Some( + #[allow(deprecated)] + ProfileFiles::builder() + .with_file( + #[allow(deprecated)] + ProfileFileKind::Config, + "conf", + ) + .build(), + ), + None, + ) + .with_fs(Fs::from_slice(&[( + "conf", + "[default]\nauth_scheme_preference = scheme1, scheme2 , \tscheme3 \t", + )])); + assert_eq!( + AuthSchemePreference::from([ + aws_runtime::auth::sigv4::SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID, + ]), + auth_scheme_preference_provider(&conf).await.unwrap() + ); + } + + #[tokio::test] + async fn load_from_profile() { + let conf = ProviderConfig::empty() + .with_profile_config( + Some( + #[allow(deprecated)] + ProfileFiles::builder() + .with_file( + #[allow(deprecated)] + ProfileFileKind::Config, + "conf", + ) + .build(), + ), + None, + ) + .with_fs(Fs::from_slice(&[( + "conf", + "[default]\nauth_scheme_preference = sigv4, httpBasicAuth, httpDigestAuth, \thttpBearerAuth \t, httpApiKeyAuth ", + )])); + assert_eq!( + AuthSchemePreference::from([ + aws_runtime::auth::sigv4::SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID, + aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID, + ]), + auth_scheme_preference_provider(&conf).await.unwrap() + ); + } + } +} diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index ff4e98af683..ba2691ad97e 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -223,6 +223,7 @@ mod loader { use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_runtime::client::identity::IdentityCache; + use aws_smithy_runtime_api::client::auth::AuthSchemePreference; use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion; use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache}; @@ -242,9 +243,10 @@ mod loader { use aws_types::SdkConfig; use crate::default_provider::{ - account_id_endpoint_mode, app_name, checksums, credentials, disable_request_compression, - endpoint_url, ignore_configured_endpoint_urls as ignore_ep, region, - request_min_compression_size_bytes, retry_config, timeout_config, use_dual_stack, use_fips, + account_id_endpoint_mode, app_name, auth_scheme_preference, checksums, credentials, + disable_request_compression, endpoint_url, ignore_configured_endpoint_urls as ignore_ep, + region, request_min_compression_size_bytes, retry_config, timeout_config, use_dual_stack, + use_fips, }; use crate::meta::region::ProvideRegion; #[allow(deprecated)] @@ -271,6 +273,7 @@ mod loader { #[derive(Default, Debug)] pub struct ConfigLoader { app_name: Option, + auth_scheme_preference: Option, identity_cache: Option, credentials_provider: TriStateOption, token_provider: Option, @@ -403,6 +406,28 @@ mod loader { self } + #[doc = docs_for!(auth_scheme_preference)] + /// + /// # Examples + /// ```no_run + /// # use aws_smithy_runtime_api::client::auth::AuthSchemeId; + /// # async fn create_config() { + /// let config = aws_config::from_env() + /// // Favors a custom auth scheme over the SigV4 auth scheme. + /// // Note: This will not result in an error, even if the custom scheme is missing from the resolved auth schemes. + /// .auth_scheme_preference([AuthSchemeId::from("custom"), aws_runtime::auth::sigv4::SCHEME_ID]) + /// .load() + /// .await; + /// # } + /// ``` + pub fn auth_scheme_preference( + mut self, + auth_scheme_preference: impl Into, + ) -> Self { + self.auth_scheme_preference = Some(auth_scheme_preference.into()); + self + } + /// Override the identity cache used to build [`SdkConfig`]. /// /// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used @@ -962,6 +987,13 @@ mod loader { account_id_endpoint_mode::account_id_endpoint_mode_provider(&conf).await }; + let auth_scheme_preference = + if let Some(auth_scheme_preference) = self.auth_scheme_preference { + Some(auth_scheme_preference) + } else { + auth_scheme_preference::auth_scheme_preference_provider(&conf).await + }; + builder.set_request_checksum_calculation(request_checksum_calculation); builder.set_response_checksum_validation(response_checksum_validation); builder.set_identity_cache(identity_cache); @@ -974,6 +1006,7 @@ mod loader { builder.set_request_min_compression_size_bytes(request_min_compression_size_bytes); builder.set_stalled_stream_protection(self.stalled_stream_protection_config); builder.set_account_id_endpoint_mode(account_id_endpoint_mode); + builder.set_auth_scheme_preference(auth_scheme_preference); builder.build() } } diff --git a/aws/rust-runtime/aws-types/Cargo.toml b/aws/rust-runtime/aws-types/Cargo.toml index f99f5e9c720..9df9c7939e8 100644 --- a/aws/rust-runtime/aws-types/Cargo.toml +++ b/aws/rust-runtime/aws-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-types" -version = "1.3.7" +version = "1.3.8" authors = ["AWS Rust SDK Team ", "Russell Cohen "] description = "Cross-service types for the AWS SDK." edition = "2021" diff --git a/aws/rust-runtime/aws-types/external-types.toml b/aws/rust-runtime/aws-types/external-types.toml index ee28a1732a5..215cc7af803 100644 --- a/aws/rust-runtime/aws-types/external-types.toml +++ b/aws/rust-runtime/aws-types/external-types.toml @@ -1,11 +1,11 @@ allowed_external_types = [ - "aws_credential_types::cache::CredentialsCache", "aws_credential_types::provider::credentials::SharedCredentialsProvider", "aws_credential_types::provider::token::SharedTokenProvider", "aws_smithy_async::rt::sleep::AsyncSleep", "aws_smithy_async::rt::sleep::SharedAsyncSleep", "aws_smithy_async::time::SharedTimeSource", "aws_smithy_async::time::TimeSource", + "aws_smithy_runtime_api::client::auth::AuthSchemePreference", "aws_smithy_runtime_api::client::behavior_version::BehaviorVersion", "aws_smithy_runtime_api::client::http::HttpClient", "aws_smithy_runtime_api::client::http::SharedHttpClient", @@ -17,7 +17,6 @@ allowed_external_types = [ "aws_smithy_types::checksum_config::RequestChecksumCalculation", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", - "aws_smithy_types::config_bag::storable::Storer", "aws_smithy_types::error::metadata::Builder", "aws_smithy_types::retry::RetryConfig", "aws_smithy_types::timeout::TimeoutConfig", diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index fc2a198155e..fc1f7080a53 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -20,6 +20,7 @@ pub use aws_credential_types::provider::SharedCredentialsProvider; use aws_smithy_async::rt::sleep::AsyncSleep; pub use aws_smithy_async::rt::sleep::SharedAsyncSleep; pub use aws_smithy_async::time::{SharedTimeSource, TimeSource}; +use aws_smithy_runtime_api::client::auth::AuthSchemePreference; use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion; use aws_smithy_runtime_api::client::http::HttpClient; pub use aws_smithy_runtime_api::client::http::SharedHttpClient; @@ -81,6 +82,17 @@ See the developer guide on [account-based endpoints](https://docs.aws.amazon.com for more information. For services that do not use the account-based endpoints, this setting does nothing. +" }; + (auth_scheme_preference) => { +"Set the auth scheme preference for an auth scheme resolver +(typically the default auth scheme resolver). + +Each operation has a predefined order of auth schemes, as determined by the service, +for auth scheme resolution. By using the auth scheme preference, customers +can reorder the schemes resolved by the auth scheme resolver. + +The preference list is intended as a hint rather than a strict override. +Any schemes not present in the originally resolved auth schemes will be ignored. " }; } } @@ -89,6 +101,7 @@ For services that do not use the account-based endpoints, this setting does noth #[derive(Debug, Clone)] pub struct SdkConfig { app_name: Option, + auth_scheme_preference: Option, identity_cache: Option, credentials_provider: Option, token_provider: Option, @@ -120,6 +133,7 @@ pub struct SdkConfig { #[derive(Debug, Default)] pub struct Builder { app_name: Option, + auth_scheme_preference: Option, identity_cache: Option, credentials_provider: Option, token_provider: Option, @@ -777,6 +791,24 @@ impl Builder { self } + #[doc = docs_for!(auth_scheme_preference)] + pub fn auth_scheme_preference( + mut self, + auth_scheme_preference: impl Into, + ) -> Self { + self.set_auth_scheme_preference(Some(auth_scheme_preference)); + self + } + + #[doc = docs_for!(auth_scheme_preference)] + pub fn set_auth_scheme_preference( + &mut self, + auth_scheme_preference: Option>, + ) -> &mut Self { + self.auth_scheme_preference = auth_scheme_preference.map(|pref| pref.into()); + self + } + /// Set the origin of a setting. /// /// This is used internally to understand how to merge config structs while @@ -789,6 +821,7 @@ impl Builder { pub fn build(self) -> SdkConfig { SdkConfig { app_name: self.app_name, + auth_scheme_preference: self.auth_scheme_preference, identity_cache: self.identity_cache, credentials_provider: self.credentials_provider, token_provider: self.token_provider, @@ -896,6 +929,11 @@ impl SdkConfig { self.account_id_endpoint_mode.as_ref() } + /// Configured auth scheme preference + pub fn auth_scheme_preference(&self) -> Option<&AuthSchemePreference> { + self.auth_scheme_preference.as_ref() + } + /// Configured endpoint URL pub fn endpoint_url(&self) -> Option<&str> { self.endpoint_url.as_deref() @@ -1020,6 +1058,7 @@ impl SdkConfig { pub fn into_builder(self) -> Builder { Builder { app_name: self.app_name, + auth_scheme_preference: self.auth_scheme_preference, identity_cache: self.identity_cache, credentials_provider: self.credentials_provider, token_provider: self.token_provider, diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt index ab00d1fc671..0cfb79aca59 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt @@ -108,6 +108,7 @@ class GenericSmithySdkConfigSettings : ClientCodegenDecorator { ${section.serviceConfigBuilder}.set_http_client(${section.sdkConfig}.http_client()); ${section.serviceConfigBuilder}.set_time_source(${section.sdkConfig}.time_source()); ${section.serviceConfigBuilder}.set_behavior_version(${section.sdkConfig}.behavior_version()); + ${section.serviceConfigBuilder}.set_auth_scheme_preference(${section.sdkConfig}.auth_scheme_preference().cloned()); // setting `None` here removes the default if let Some(config) = ${section.sdkConfig}.stalled_stream_protection() { ${section.serviceConfigBuilder}.set_stalled_stream_protection(Some(config)); diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt index c353f2a8493..f458c472975 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt @@ -62,7 +62,8 @@ class AuthDecorator : ClientCodegenDecorator { override fun configCustomizations( codegenContext: ClientCodegenContext, baseCustomizations: List, - ): List = baseCustomizations + AuthDecoratorConfigCustomizations(codegenContext) + ): List = + baseCustomizations + AuthDecoratorConfigCustomizations(codegenContext) + AuthSchemePreferenceConfigCustomization(codegenContext) override fun serviceRuntimePluginCustomizations( codegenContext: ClientCodegenContext, @@ -74,7 +75,10 @@ class AuthDecorator : ClientCodegenDecorator { return when (section) { is ServiceRuntimePluginSection.RegisterRuntimeComponents -> writable { - section.registerAuthSchemeOptionResolver(this, defaultAuthSchemeResolver(codegenContext)) + section.registerAuthSchemeOptionResolver( + this, + defaultAuthSchemeResolver(codegenContext, section.serviceConfigName), + ) } else -> emptySection @@ -85,14 +89,24 @@ class AuthDecorator : ClientCodegenDecorator { } // Returns default auth scheme resolver for this service -private fun defaultAuthSchemeResolver(codegenContext: ClientCodegenContext): Writable { +private fun defaultAuthSchemeResolver( + codegenContext: ClientCodegenContext, + serviceConfigName: String, +): Writable { val generator = AuthTypesGenerator(codegenContext) return writable { rustTemplate( """{ use #{ServiceSpecificResolver}; - #{DefaultResolver}::default().into_shared_resolver() + if let Some(preference) = $serviceConfigName.config.load::<#{AuthSchemePreference}>().cloned() { + #{DefaultResolver}::default().with_auth_scheme_preference(preference).into_shared_resolver() + } else { + #{DefaultResolver}::default().into_shared_resolver() + } }""", + "AuthSchemePreference" to + RuntimeType.smithyRuntimeApiClient(codegenContext.runtimeConfig) + .resolve("client::auth::AuthSchemePreference"), "DefaultResolver" to generator.defaultAuthSchemeResolver(), "ServiceSpecificResolver" to generator.serviceSpecificResolveAuthSchemeTrait(), ) @@ -299,3 +313,80 @@ private class AuthDecoratorConfigCustomizations(private val codegenContext: Clie } } } + +private class AuthSchemePreferenceConfigCustomization(codegenContext: ClientCodegenContext) : ConfigCustomization() { + private val codegenScope = + arrayOf( + *preludeScope, + "AuthSchemePreference" to + RuntimeType.smithyRuntimeApiClient(codegenContext.runtimeConfig) + .resolve("client::auth::AuthSchemePreference"), + ) + private val moduleUseName = codegenContext.moduleUseName() + + override fun section(section: ServiceConfig) = + writable { + when (section) { + is ServiceConfig.ConfigImpl -> { + rustTemplate( + """ + /// Returns the configured auth scheme preference + pub fn auth_scheme_preference(&self) -> #{Option}<&#{AuthSchemePreference}> { + self.config.load::<#{AuthSchemePreference}>() + } + """, + *codegenScope, + ) + } + + is ServiceConfig.BuilderImpl -> { + val docs = """ + /// Set the auth scheme preference for an auth scheme resolver + /// (typically the default auth scheme resolver). + /// + /// Each operation has a predefined order of auth schemes, as determined by the service, + /// for auth scheme resolution. By using the auth scheme preference, customers + /// can reorder the schemes resolved by the auth scheme resolver. + /// + /// The preference list is intended as a hint rather than a strict override. + /// Any schemes not present in the originally resolved auth schemes will be ignored. + /// + /// ## Examples + /// + /// ```no_run + /// ## use aws_smithy_runtime_api::client::auth::AuthSchemeId; + /// let config = $moduleUseName::Config::builder() + /// .auth_scheme_preference([AuthSchemeId::from("scheme1"), AuthSchemeId::from("scheme2")]) + /// // ... + /// .build(); + /// let client = $moduleUseName::Client::from_conf(config); + /// ``` + """ + rustTemplate( + """ + $docs + pub fn auth_scheme_preference(mut self, preference: impl #{Into}<#{AuthSchemePreference}>) -> Self { + self.set_auth_scheme_preference(#{Some}(preference.into())); + self + } + + $docs + pub fn set_auth_scheme_preference(&mut self, preference: #{Option}<#{AuthSchemePreference}>) -> &mut Self { + self.config.store_or_unset(preference); + self + } + """, + *codegenScope, + ) + } + + is ServiceConfig.BuilderFromConfigBag -> + rustTemplate( + "${section.builder}.set_auth_scheme_preference(${section.configBag}.load::<#{AuthSchemePreference}>().cloned());", + *codegenScope, + ) + + else -> emptySection + } + } +} From b6608c23def3caadac1e87a6a99e7cdd0fbe51a6 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 9 Jul 2025 21:56:33 -0500 Subject: [PATCH 04/10] Add tests for `resolve_identity` --- rust-runtime/aws-smithy-runtime/Cargo.toml | 2 +- .../src/client/orchestrator/auth.rs | 114 ++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index ef735e3e348..eb7b50c4a1d 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-runtime" -version = "1.8.4" +version = "1.8.5" authors = ["AWS Rust SDK Team ", "Zelda Hessler "] description = "The new smithy runtime crate" edition = "2021" diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs index 0e2f69db29b..01312e1566a 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs @@ -885,4 +885,118 @@ mod tests { panic!("The error should indicate that the explored list was truncated."); } } + + #[cfg(feature = "http-auth")] + #[tokio::test] + async fn test_resolve_identity() { + use crate::client::auth::http::{ApiKeyAuthScheme, ApiKeyLocation, BasicAuthScheme}; + use aws_smithy_runtime_api::client::auth::http::{ + HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID, + }; + use aws_smithy_runtime_api::client::identity::http::Token; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; + + #[derive(Debug)] + struct Cache; + impl ResolveCachedIdentity for Cache { + fn resolve_cached_identity<'a>( + &'a self, + identity_resolver: SharedIdentityResolver, + rc: &'a RuntimeComponents, + cfg: &'a ConfigBag, + ) -> IdentityFuture<'a> { + IdentityFuture::new( + async move { identity_resolver.resolve_identity(rc, cfg).await }, + ) + } + } + + let mut layer = Layer::new("test"); + layer.store_put(AuthSchemeAndEndpointOrchestrationV2); + layer.store_put(AuthSchemeOptionResolverParams::new("doesntmatter")); + let mut cfg = ConfigBag::of_layers(vec![layer]); + + let runtime_components_builder = RuntimeComponentsBuilder::for_tests() + .with_auth_scheme(SharedAuthScheme::new(BasicAuthScheme::new())) + .with_auth_scheme(SharedAuthScheme::new(ApiKeyAuthScheme::new( + "result:", + ApiKeyLocation::Header, + "Authorization", + ))) + .with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new( + StaticAuthSchemeOptionResolver::new(vec![ + HTTP_BASIC_AUTH_SCHEME_ID, + HTTP_API_KEY_AUTH_SCHEME_ID, + ]), + ))) + .with_identity_cache(Some(Cache)); + + struct TestCase { + builder_updater: Box RuntimeComponents>, + resolved_auth_scheme: AuthSchemeId, + should_error: bool, + } + + for test_case in [ + TestCase { + builder_updater: Box::new(|rcb: RuntimeComponentsBuilder| { + rcb.with_identity_resolver( + HTTP_BASIC_AUTH_SCHEME_ID, + SharedIdentityResolver::new(Token::new("basic", None)), + ) + .with_identity_resolver( + HTTP_API_KEY_AUTH_SCHEME_ID, + SharedIdentityResolver::new(Token::new("api-key", None)), + ) + .build() + .unwrap() + }), + resolved_auth_scheme: HTTP_BASIC_AUTH_SCHEME_ID, + should_error: false, + }, + TestCase { + builder_updater: Box::new(|rcb: RuntimeComponentsBuilder| { + rcb.with_identity_resolver( + HTTP_BASIC_AUTH_SCHEME_ID, + SharedIdentityResolver::new(Token::new("basic", None)), + ) + .build() + .unwrap() + }), + resolved_auth_scheme: HTTP_BASIC_AUTH_SCHEME_ID, + should_error: false, + }, + TestCase { + builder_updater: Box::new(|rcb: RuntimeComponentsBuilder| { + rcb.with_identity_resolver( + HTTP_API_KEY_AUTH_SCHEME_ID, + SharedIdentityResolver::new(Token::new("api-key", None)), + ) + .build() + .unwrap() + }), + resolved_auth_scheme: HTTP_API_KEY_AUTH_SCHEME_ID, + should_error: false, + }, + TestCase { + builder_updater: Box::new(|rcb: RuntimeComponentsBuilder| rcb.build().unwrap()), + resolved_auth_scheme: HTTP_API_KEY_AUTH_SCHEME_ID, + should_error: true, + }, + ] + .into_iter() + { + let runtime_components = + (test_case.builder_updater)(runtime_components_builder.clone()); + match resolve_identity(&runtime_components, &mut cfg).await { + Ok(resolved) => assert_eq!(test_case.resolved_auth_scheme, resolved.0), + Err(e) if test_case.should_error => { + assert!(e.downcast_ref::().is_some()); + } + _ => { + panic!("`resolve_identity` returned an `Err` when no error was expected in the test."); + } + } + } + } } From 6626ce8fff92b04e2c5f11e86e9f2d1be4394b3d Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 9 Jul 2025 21:57:05 -0500 Subject: [PATCH 05/10] Add test for auth scheme preference priority --- .../s3/tests/auth_scheme_preference.rs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs diff --git a/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs b/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs new file mode 100644 index 00000000000..21b73c66fc6 --- /dev/null +++ b/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs @@ -0,0 +1,34 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_config::Region; +use aws_sdk_s3::Client; +use aws_smithy_http_client::test_util::capture_request; + +#[tracing_test::traced_test] +#[tokio::test] +async fn auth_scheme_preference_should_take_the_highest_priority() { + let (http_client, _) = capture_request(None); + let config = aws_config::from_env() + .http_client(http_client) + .region(Region::new("us-east-2")) + // Explicitly set a preference that favors `sigv4`, otherwise `sigv4a` + // would normally be resolved based on the endpoint authSchemes property. + .auth_scheme_preference([aws_runtime::auth::sigv4::SCHEME_ID]) + .load() + .await; + + let client = Client::new(&config); + let _ = client + .get_object() + .bucket("arn:aws:s3::123456789012:accesspoint/mfzwi23gnjvgw.mrap") + .key("doesnotmatter") + .send() + .await; + + assert!(logs_contain( + "resolving identity scheme_id=AuthSchemeId { scheme_id: \"sigv4\" }" + )); +} From a562638cb954071808c8908a73292885a41137c0 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 9 Jul 2025 21:57:20 -0500 Subject: [PATCH 06/10] Update lockfiles --- aws/rust-runtime/Cargo.lock | 8 ++++---- rust-runtime/Cargo.lock | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aws/rust-runtime/Cargo.lock b/aws/rust-runtime/Cargo.lock index 3f8d9cbcdbd..bde3058f9d3 100644 --- a/aws/rust-runtime/Cargo.lock +++ b/aws/rust-runtime/Cargo.lock @@ -222,7 +222,7 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.5" +version = "1.3.0" dependencies = [ "futures-util", "pin-project-lite", @@ -319,7 +319,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.4" +version = "1.8.5" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -341,7 +341,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.2" +version = "1.8.4" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -380,7 +380,7 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.7" +version = "1.3.8" dependencies = [ "aws-credential-types", "aws-smithy-async", diff --git a/rust-runtime/Cargo.lock b/rust-runtime/Cargo.lock index b7feea3a597..8f3157b7d40 100644 --- a/rust-runtime/Cargo.lock +++ b/rust-runtime/Cargo.lock @@ -312,7 +312,7 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.5" +version = "1.3.0" dependencies = [ "futures-util", "pin-project-lite", @@ -613,7 +613,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.4" +version = "1.8.5" dependencies = [ "approx", "aws-smithy-async", @@ -641,7 +641,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.3" +version = "1.8.4" dependencies = [ "aws-smithy-async", "aws-smithy-types", From 2d483a0880c30683858329fc8411e9f9282de633 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Thu, 10 Jul 2025 10:43:38 -0500 Subject: [PATCH 07/10] Add changelog entry --- .changelog/1752160009.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .changelog/1752160009.md diff --git a/.changelog/1752160009.md b/.changelog/1752160009.md new file mode 100644 index 00000000000..ac99af7532a --- /dev/null +++ b/.changelog/1752160009.md @@ -0,0 +1,37 @@ +--- +applies_to: +- aws-sdk-rust +- client +authors: +- ysaito1001 +references: +- smithy-rs#4203 +breaking: false +new_feature: true +bug_fix: false +--- +Add support for configuring auth schemes manually using an auth scheme preference list. +The preference list allows customers to reprioritize the order of auth schemes originally +determined by the auth scheme resolver. +Customers can configure the auth scheme preference at the following locations, listed in order of precedence: +1. Service Client Configuration +```rust +use aws_runtime::auth::sigv4; +use aws_smithy_runtime_api::client::auth::AuthSchemeId; +use aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID; + +let config = aws_sdk_s3::Config::builder() + .auth_scheme_preference([AuthSchemeId::from("scheme1"), sigv4::SCHEME_ID, HTTP_BEARER_AUTH_SCHEME_ID]) + // ... + .build(); +``` +2. Environment Variable +``` +AWS_AUTH_SCHEME_PREFERENCE=scheme1, sigv4, httpBearerAuth +``` +3. Configuration File +``` +auth_scheme_preference=scheme1, sigv4, httpBearerAuth +``` +With this configuration, the auth scheme resolver will prefer to select them in the specified order, +if they are supported. From e5b48cbdfd53c3709ae479fa26adab7b2e96814a Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Thu, 10 Jul 2025 20:39:36 -0500 Subject: [PATCH 08/10] Revert "Add ability to map boxed future in `new_type_future`" This reverts commit 842ee41feb77162624d8c66ff5d76fa97d67b70a. --- rust-runtime/aws-smithy-async/Cargo.toml | 2 +- .../src/future/now_or_later.rs | 74 ---------- .../aws-smithy-runtime-api/Cargo.toml | 2 +- .../aws-smithy-runtime-api/src/client/auth.rs | 126 +----------------- 4 files changed, 3 insertions(+), 201 deletions(-) diff --git a/rust-runtime/aws-smithy-async/Cargo.toml b/rust-runtime/aws-smithy-async/Cargo.toml index 9462e013af6..c2cde40d505 100644 --- a/rust-runtime/aws-smithy-async/Cargo.toml +++ b/rust-runtime/aws-smithy-async/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-async" -version = "1.3.0" +version = "1.2.5" authors = ["AWS Rust SDK Team ", "John DiSanti "] description = "Async runtime agnostic abstractions for smithy-rs." edition = "2021" diff --git a/rust-runtime/aws-smithy-async/src/future/now_or_later.rs b/rust-runtime/aws-smithy-async/src/future/now_or_later.rs index 231aa693e59..a30e9435534 100644 --- a/rust-runtime/aws-smithy-async/src/future/now_or_later.rs +++ b/rust-runtime/aws-smithy-async/src/future/now_or_later.rs @@ -125,66 +125,6 @@ impl NowOrLater { } } -impl<'a, T, F> NowOrLater -where - F: Future + Send + 'a, -{ - /// Maps the value inside `NowOrLater` and returns a boxed future with lifetime `'a`. - /// - /// Examples - /// - /// ```no_run - /// # use aws_smithy_async::future::now_or_later::{NowOrLater, BoxFuture}; - /// # async fn map_boxed_later_variant() { - /// let later = NowOrLater::new(async { 10 }); - /// let mapped = later.map_boxed(|x| x + 5); - /// assert_eq!(15, mapped.await); - /// # } - /// ``` - pub fn map_boxed(self, map_fn: M) -> NowOrLater> - where - M: FnOnce(T) -> U + Send + 'a, - { - match self.inner { - Inner::Now { value } => { - let mapped = value.map(map_fn); - NowOrLater { - inner: Inner::Now { value: mapped }, - } - } - Inner::Later { future } => { - let fut = async move { - let val = future.await; - map_fn(val) - }; - NowOrLater { - inner: Inner::Later { - future: Box::pin(fut), - }, - } - } - } - } -} - -impl NowOrLater { - /// Like [`NowOrLater::map_boxed`], but specialized for use with [`OnlyReady`] - pub fn map_boxed(self, map_fn: M) -> NowOrLater - where - M: FnOnce(T) -> U, - { - match self.inner { - Inner::Now { value } => { - let mapped = value.map(map_fn); - NowOrLater { - inner: Inner::Now { value: mapped }, - } - } - Inner::Later { .. } => unreachable!(), - } - } -} - impl Future for NowOrLater where F: Future, @@ -254,18 +194,4 @@ mod test { let wrapped = NowOrLater::new(async { 5 }); assert_eq!(wrapped.await, 5); } - - #[tokio::test] - async fn map_boxed_now_variant() { - let now: NowOrLater = NowOrLater::ready(21); - let mapped = now.map_boxed(|x| x * 2); - assert_eq!(42, mapped.await); - } - - #[tokio::test] - async fn map_boxed_later_variant() { - let later = NowOrLater::new(async { 10 }); - let mapped = later.map_boxed(|x| x + 5); - assert_eq!(15, mapped.await); - } } diff --git a/rust-runtime/aws-smithy-runtime-api/Cargo.toml b/rust-runtime/aws-smithy-runtime-api/Cargo.toml index 86d83300ccf..bebdc29dfa9 100644 --- a/rust-runtime/aws-smithy-runtime-api/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-runtime-api" -version = "1.8.4" +version = "1.8.3" authors = ["AWS Rust SDK Team ", "Zelda Hessler "] description = "Smithy runtime types." edition = "2021" diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs b/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs index 77765affb79..183d36602ec 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs @@ -15,7 +15,7 @@ use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Storable, StoreReplac use aws_smithy_types::type_erasure::TypeErasedBox; use aws_smithy_types::Document; use std::borrow::Cow; -use std::fmt::{self, Debug}; +use std::fmt; use std::sync::Arc; /// Auth schemes for the HTTP `Authorization` header. @@ -225,25 +225,6 @@ new_type_future! { pub struct AuthSchemeOptionsFuture<'a, Vec, BoxError>; } -// Currently, we don't add `map_ok` to the `new_type_future` macro in general. -// It's specifically used for `AuthSchemeOptionsFuture`, but we can expand it later if needed. -impl<'a> AuthSchemeOptionsFuture<'a> { - /// Transforms the `Ok` variant inside this `AuthSchemeOptionsFuture` by applying the provided function. - /// - /// This method maps over the `Ok` variant of the `Result` wrapped by the future, - /// applying `map_fn` to the contained `Vec`. - /// - /// The transformation is applied regardless of whether the future's value is already - /// available (`Now`) or will be computed asynchronously (`Later`). - pub fn map_ok(self, f: F) -> AuthSchemeOptionsFuture<'a> - where - F: FnOnce(Vec) -> Vec + Send + 'a, - { - let inner = self.inner.map_boxed(|result| result.map(f)); - Self { inner } - } -} - /// Resolver for auth scheme options. /// /// The orchestrator needs to select an auth scheme to sign requests with, and potentially @@ -439,108 +420,3 @@ impl<'a> From<&'a Document> for AuthSchemeEndpointConfig<'a> { Self(Some(value)) } } - -/// An ordered list of [AuthSchemeId]s -/// -/// Can be used to reorder already-resolved auth schemes by an auth scheme resolver. -/// This list is intended as a hint rather than a strict override; -/// any schemes not present in the resolved auth schemes will be ignored. -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct AuthSchemePreference { - preference_list: Vec, -} - -impl Storable for AuthSchemePreference { - type Storer = StoreReplace; -} - -impl IntoIterator for AuthSchemePreference { - type Item = AuthSchemeId; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.preference_list.into_iter() - } -} - -impl From for AuthSchemePreference -where - T: AsRef<[AuthSchemeId]>, -{ - fn from(slice: T) -> Self { - AuthSchemePreference { - preference_list: slice.as_ref().to_vec(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_map_ok_now_variant() { - let input = ["sigv4", "http"] - .map(|s| AuthSchemeOption::from(AuthSchemeId::from(s))) - .to_vec(); - let fut = AuthSchemeOptionsFuture::ready(Ok(input)); - - let mapped = fut.map_ok(|opts| { - opts.into_iter() - .filter(|opt| opt.scheme_id().inner() != "http") - .collect() - }); - - let result = mapped.await; - assert!(result.is_ok()); - let vec = result.unwrap(); - assert_eq!(1, vec.len()); - assert_eq!("sigv4", vec[0].scheme_id().inner()); - } - - #[tokio::test] - async fn test_map_ok_now_variant_error_no_op() { - let fut = AuthSchemeOptionsFuture::ready(Err(BoxError::from("oops"))); - - let mapped = fut.map_ok(|opts| opts); // no-op - - let result = mapped.await; - assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "oops"); - } - - #[tokio::test] - async fn test_map_ok_later_variant() { - let input = ["foo", "bar"] - .map(|s| AuthSchemeOption::from(AuthSchemeId::from(s))) - .to_vec(); - let fut = AuthSchemeOptionsFuture::new(async move { Ok(input) }); - - let mapped = fut.map_ok(|opts| { - opts.into_iter() - .map(|mut opt| { - opt.scheme_id = - AuthSchemeId::from(Cow::Owned(opt.scheme_id().inner().to_uppercase())); - opt - }) - .collect() - }); - - let result = mapped.await; - assert!(result.is_ok()); - let vec = result.unwrap(); - assert_eq!(vec[0].scheme_id().inner(), "FOO"); - assert_eq!(vec[1].scheme_id().inner(), "BAR"); - } - - #[tokio::test] - async fn test_map_ok_later_variant_error_no_op() { - let fut = AuthSchemeOptionsFuture::new(async move { Err(BoxError::from("later fail")) }); - - let mapped = fut.map_ok(|opts| opts); // no-op - - let result = mapped.await; - assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "later fail"); - } -} From d07260e35403d5cc8419898e11d8a5f074b9b4ae Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Fri, 11 Jul 2025 10:59:15 -0500 Subject: [PATCH 09/10] Use auth scheme preference in orchestrator This commit addresses https://github.com/smithy-lang/smithy-rs/pull/4203#discussion_r2198517040 --- .../s3/tests/auth_scheme_preference.rs | 36 ++++- .../client/smithy/auth/AuthDecorator.kt | 16 +- .../auth/AuthSchemeResolverGenerator.kt | 41 +----- .../smithy/auth/AuthTypesGeneratorTest.kt | 115 --------------- rust-runtime/Cargo.lock | 2 +- .../aws-smithy-runtime-api/Cargo.toml | 2 +- .../aws-smithy-runtime-api/src/client/auth.rs | 34 +++++ .../src/client/orchestrator/auth.rs | 137 +++++++++++++++++- 8 files changed, 209 insertions(+), 174 deletions(-) diff --git a/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs b/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs index 21b73c66fc6..f06c0bdf0ad 100644 --- a/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs +++ b/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs @@ -4,12 +4,15 @@ */ use aws_config::Region; -use aws_sdk_s3::Client; +use aws_sdk_s3::{Client, Config}; use aws_smithy_http_client::test_util::capture_request; +// S3 is one of the servies that relies on endpoint-based auth scheme resolution. +// An auth scheme preference should not be overridden by other resolution methods. + #[tracing_test::traced_test] #[tokio::test] -async fn auth_scheme_preference_should_take_the_highest_priority() { +async fn auth_scheme_preference_at_client_level_should_take_the_highest_priority() { let (http_client, _) = capture_request(None); let config = aws_config::from_env() .http_client(http_client) @@ -32,3 +35,32 @@ async fn auth_scheme_preference_should_take_the_highest_priority() { "resolving identity scheme_id=AuthSchemeId { scheme_id: \"sigv4\" }" )); } + +#[tracing_test::traced_test] +#[tokio::test] +async fn auth_scheme_preference_at_operation_level_should_take_the_highest_priority() { + let (http_client, _) = capture_request(None); + let config = aws_config::from_env() + .http_client(http_client) + .region(Region::new("us-east-2")) + .load() + .await; + + let client = Client::new(&config); + let _ = client + .get_object() + .bucket("arn:aws:s3::123456789012:accesspoint/mfzwi23gnjvgw.mrap") + .key("doesnotmatter") + .customize() + .config_override( + // Explicitly set a preference that favors `sigv4`, otherwise `sigv4a` + // would normally be resolved based on the endpoint authSchemes property. + Config::builder().auth_scheme_preference([aws_runtime::auth::sigv4::SCHEME_ID]), + ) + .send() + .await; + + assert!(logs_contain( + "resolving identity scheme_id=AuthSchemeId { scheme_id: \"sigv4\" }" + )); +} diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt index f458c472975..b0d3aadfeb6 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthDecorator.kt @@ -77,7 +77,7 @@ class AuthDecorator : ClientCodegenDecorator { writable { section.registerAuthSchemeOptionResolver( this, - defaultAuthSchemeResolver(codegenContext, section.serviceConfigName), + defaultAuthSchemeResolver(codegenContext), ) } @@ -89,24 +89,14 @@ class AuthDecorator : ClientCodegenDecorator { } // Returns default auth scheme resolver for this service -private fun defaultAuthSchemeResolver( - codegenContext: ClientCodegenContext, - serviceConfigName: String, -): Writable { +private fun defaultAuthSchemeResolver(codegenContext: ClientCodegenContext): Writable { val generator = AuthTypesGenerator(codegenContext) return writable { rustTemplate( """{ use #{ServiceSpecificResolver}; - if let Some(preference) = $serviceConfigName.config.load::<#{AuthSchemePreference}>().cloned() { - #{DefaultResolver}::default().with_auth_scheme_preference(preference).into_shared_resolver() - } else { - #{DefaultResolver}::default().into_shared_resolver() - } + #{DefaultResolver}::default().into_shared_resolver() }""", - "AuthSchemePreference" to - RuntimeType.smithyRuntimeApiClient(codegenContext.runtimeConfig) - .resolve("client::auth::AuthSchemePreference"), "DefaultResolver" to generator.defaultAuthSchemeResolver(), "ServiceSpecificResolver" to generator.serviceSpecificResolveAuthSchemeTrait(), ) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt index 98bd4a227ec..5eb4b86360a 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthSchemeResolverGenerator.kt @@ -54,7 +54,6 @@ class AuthSchemeResolverGenerator( .resolve("client::auth::AuthSchemeOption"), "AuthSchemeOptionResolverParams" to RuntimeType.smithyRuntimeApiClient(codegenContext.runtimeConfig).resolve("client::auth::AuthSchemeOptionResolverParams"), "AuthSchemeOptionsFuture" to RuntimeType.smithyRuntimeApiClient(runtimeConfig).resolve("client::auth::AuthSchemeOptionsFuture"), - "AuthSchemePreference" to RuntimeType.smithyRuntimeApiClient(runtimeConfig).resolve("client::auth::AuthSchemePreference"), "BoxError" to RuntimeType.boxError(runtimeConfig), "ConfigBag" to RuntimeType.configBag(codegenContext.runtimeConfig), "Debug" to RuntimeType.Debug, @@ -79,7 +78,6 @@ class AuthSchemeResolverGenerator( pub struct DefaultAuthSchemeResolver { service_defaults: Vec<#{AuthSchemeOption}>, operation_overrides: #{HashMap}<&'static str, Vec<#{AuthSchemeOption}>>, - preference: #{Option}<#{AuthSchemePreference}>, } // TODO(https://github.com/smithy-lang/smithy-rs/issues/4177): Remove `allow(...)` once the issue is addressed. @@ -92,7 +90,6 @@ class AuthSchemeResolverGenerator( Self { service_defaults: vec![#{service_defaults:W}], operation_overrides: #{operation_overrides:W}, - preference: #{None}, } } } @@ -115,43 +112,7 @@ class AuthSchemeResolverGenerator( #{additional_impl:W} - match &self.preference { - #{Some}(preference) => { - _fut.map_ok({ - // maps auth scheme ID to the index in the preference list - let preference_map: #{HashMap}<_, _> = preference - .clone() - .into_iter() - .enumerate() - .map(|(i, s)| (s, i)) - .collect(); - move |auth_scheme_options| { - let (mut preferred, non_preferred): (#{Vec}<_>, #{Vec}<_>) = auth_scheme_options - .into_iter() - .partition(|auth_scheme_option| { - preference_map.contains_key(auth_scheme_option.scheme_id()) - }); - - preferred.sort_by_key(|opt| { - preference_map - .get(opt.scheme_id()) - .expect("guaranteed by `partition`") - }); - preferred.extend(non_preferred); - preferred - } - }) - }, - #{None} => _fut, - } - } - } - - impl DefaultAuthSchemeResolver { - /// Set auth scheme preference to the default auth scheme resolver - pub fn with_auth_scheme_preference(mut self, preference: impl #{Into}<#{AuthSchemePreference}>) -> Self { - self.preference = #{Some}(preference.into()); - self + _fut } } """, diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt index d4d4acd4e85..7f638d17d31 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt @@ -135,119 +135,4 @@ class AuthTypesGeneratorTest { } project.compileAndTest() } - - @Test - fun `auth scheme preference`() { - val ctx = - testClientCodegenContext( - model, - rootDecorator = - CombinedClientCodegenDecorator( - listOf( - NoAuthDecorator(), - HttpAuthDecorator(), - ), - ), - ) - val sut = AuthTypesGenerator(ctx) - val project = TestWorkspace.testProject() - project.withModule(RustModule.private("auth_scheme_preference").cfgTest()) { - val smithyApiClient = - CargoDependency.smithyRuntimeApiClient(ctx.runtimeConfig) - .copy(features = setOf("http-auth", "test-util"), scope = DependencyScope.Dev).toType() - val codegenScope = - arrayOf( - "AuthSchemeId" to smithyApiClient.resolve("client::auth::AuthSchemeId"), - "ConfigBag" to - CargoDependency.smithyTypes(ctx.runtimeConfig) - .copy(features = setOf("test-util"), scope = DependencyScope.Dev).toType() - .resolve("config_bag::ConfigBag"), - "DefaultAuthSchemeResolver" to sut.defaultAuthSchemeResolver(), - "HTTP_API_KEY_AUTH_SCHEME_ID" to smithyApiClient.resolve("client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID"), - "HTTP_BASIC_AUTH_SCHEME_ID" to smithyApiClient.resolve("client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID"), - "HTTP_BEARER_AUTH_SCHEME_ID" to - smithyApiClient - .resolve("client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID"), - "Params" to AuthSchemeParamsGenerator(ctx).paramsStruct(), - "RuntimeComponentsBuilder" to - smithyApiClient - .resolve("client::runtime_components::RuntimeComponentsBuilder"), - "ServiceSpecificResolver" to sut.serviceSpecificResolveAuthSchemeTrait(), - ) - rustTemplate("use #{ServiceSpecificResolver};", *codegenScope) - tokioTest("test_auth_scheme_preference") { - rustTemplate( - """ - struct TestCase<'a> { - auth_scheme_params: &'a #{Params}, - preference_list: &'a[#{AuthSchemeId}], - expected_resolved_auths: Vec<#{AuthSchemeId}>, - } - - let get_foo_service_default_params = #{Params}::builder() - .operation_name("GetFooServiceDefault") - .build() - .unwrap(); - let get_foo_op_override_params = #{Params}::builder() - .operation_name("GetFooOpOverride") - .build() - .unwrap(); - let cfg = #{ConfigBag}::base(); - let rc = - #{RuntimeComponentsBuilder}::for_tests() - .build() - .unwrap(); - - for test_case in [ - TestCase { - auth_scheme_params: &get_foo_service_default_params, - preference_list: &[], - expected_resolved_auths: vec![#{HTTP_API_KEY_AUTH_SCHEME_ID}, #{HTTP_BASIC_AUTH_SCHEME_ID}], - }, - TestCase { - auth_scheme_params: &get_foo_service_default_params, - preference_list: &["bogus".into()], - expected_resolved_auths: vec![#{HTTP_API_KEY_AUTH_SCHEME_ID}, #{HTTP_BASIC_AUTH_SCHEME_ID}], - }, - TestCase { - auth_scheme_params: &get_foo_service_default_params, - preference_list: &[#{HTTP_BASIC_AUTH_SCHEME_ID}], - expected_resolved_auths: vec![#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_API_KEY_AUTH_SCHEME_ID}], - }, - TestCase { - auth_scheme_params: &get_foo_service_default_params, - preference_list: &[#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_API_KEY_AUTH_SCHEME_ID}], - expected_resolved_auths: vec![#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_API_KEY_AUTH_SCHEME_ID}], - }, - TestCase { - auth_scheme_params: &get_foo_op_override_params, - preference_list: &[#{HTTP_BEARER_AUTH_SCHEME_ID}], - expected_resolved_auths: vec![#{HTTP_BEARER_AUTH_SCHEME_ID}, #{HTTP_BASIC_AUTH_SCHEME_ID}], - }, - TestCase { - auth_scheme_params: &get_foo_op_override_params, - preference_list: &[#{HTTP_API_KEY_AUTH_SCHEME_ID}], - expected_resolved_auths: vec![#{HTTP_BASIC_AUTH_SCHEME_ID}, #{HTTP_BEARER_AUTH_SCHEME_ID}], - }, - ].into_iter() { - let sut = #{DefaultAuthSchemeResolver}::default() - .with_auth_scheme_preference(test_case.preference_list); - let actual = sut - .resolve_auth_scheme(&test_case.auth_scheme_params, &cfg, &rc) - .await - .unwrap(); - let actual = actual - .iter() - .map(|opt| opt.scheme_id()) - .cloned() - .collect::>(); - assert_eq!(test_case.expected_resolved_auths, actual); - } - """, - *codegenScope, - ) - } - } - project.compileAndTest() - } } diff --git a/rust-runtime/Cargo.lock b/rust-runtime/Cargo.lock index 8f3157b7d40..6e7c635207c 100644 --- a/rust-runtime/Cargo.lock +++ b/rust-runtime/Cargo.lock @@ -312,7 +312,7 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.3.0" +version = "1.2.5" dependencies = [ "futures-util", "pin-project-lite", diff --git a/rust-runtime/aws-smithy-runtime-api/Cargo.toml b/rust-runtime/aws-smithy-runtime-api/Cargo.toml index bebdc29dfa9..86d83300ccf 100644 --- a/rust-runtime/aws-smithy-runtime-api/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-runtime-api" -version = "1.8.3" +version = "1.8.4" authors = ["AWS Rust SDK Team ", "Zelda Hessler "] description = "Smithy runtime types." edition = "2021" diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs b/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs index 183d36602ec..4ca15dcc78c 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/auth.rs @@ -420,3 +420,37 @@ impl<'a> From<&'a Document> for AuthSchemeEndpointConfig<'a> { Self(Some(value)) } } + +/// An ordered list of [AuthSchemeId]s +/// +/// Can be used to reorder already-resolved auth schemes by an auth scheme resolver. +/// This list is intended as a hint rather than a strict override; +/// any schemes not present in the resolved auth schemes will be ignored. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AuthSchemePreference { + preference_list: Vec, +} + +impl Storable for AuthSchemePreference { + type Storer = StoreReplace; +} + +impl IntoIterator for AuthSchemePreference { + type Item = AuthSchemeId; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.preference_list.into_iter() + } +} + +impl From for AuthSchemePreference +where + T: AsRef<[AuthSchemeId]>, +{ + fn from(slice: T) -> Self { + AuthSchemePreference { + preference_list: slice.as_ref().to_vec(), + } + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs index 01312e1566a..ecef661d0c6 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/auth.rs @@ -7,8 +7,8 @@ use crate::client::auth::no_auth::NO_AUTH_SCHEME_ID; use crate::client::identity::IdentityCache; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::auth::{ - AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, AuthSchemeOptionResolverParams, - ResolveAuthSchemeOptions, + AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, AuthSchemeOption, + AuthSchemeOptionResolverParams, AuthSchemePreference, ResolveAuthSchemeOptions, }; use aws_smithy_runtime_api::client::endpoint::{EndpointResolverParams, ResolveEndpoint}; use aws_smithy_runtime_api::client::identity::{Identity, ResolveIdentity}; @@ -19,6 +19,7 @@ use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace}; use aws_smithy_types::endpoint::Endpoint; use aws_smithy_types::Document; use std::borrow::Cow; +use std::collections::HashMap; use std::error::Error as StdError; use std::fmt; use tracing::trace; @@ -125,6 +126,8 @@ pub(super) async fn resolve_identity( let options = option_resolver .resolve_auth_scheme_options_v2(params, cfg, runtime_components) .await?; + let options = + reprioritize_with_auth_scheme_preference(options, cfg.load::()).await; trace!( auth_scheme_option_resolver_params = ?params, @@ -185,6 +188,40 @@ pub(super) async fn resolve_identity( Err(NoMatchingAuthSchemeError(explored).into()) } +// Re-prioritize `supported_auth_scheme_options` based on `auth_scheme_preference` +// +// Schemes in `auth_scheme_preference` that are not present in `supported_auth_scheme_options` will be ignored. +async fn reprioritize_with_auth_scheme_preference( + supported_auth_scheme_options: Vec, + auth_scheme_preference: Option<&AuthSchemePreference>, +) -> Vec { + match auth_scheme_preference { + Some(preference) => { + // maps auth scheme ID to the index in the preference list + let preference_map: HashMap<_, _> = preference + .clone() + .into_iter() + .enumerate() + .map(|(i, s)| (s, i)) + .collect(); + let (mut preferred, non_preferred): (Vec<_>, Vec<_>) = supported_auth_scheme_options + .into_iter() + .partition(|auth_scheme_option| { + preference_map.contains_key(auth_scheme_option.scheme_id()) + }); + + preferred.sort_by_key(|opt| { + preference_map + .get(opt.scheme_id()) + .expect("guaranteed by `partition`") + }); + preferred.extend(non_preferred); + preferred + } + None => supported_auth_scheme_options, + } +} + pub(super) fn sign_request( scheme_id: &AuthSchemeId, identity: &Identity, @@ -999,4 +1036,100 @@ mod tests { } } } + + #[cfg(feature = "http-auth")] + #[tokio::test] + async fn auth_scheme_preference() { + use aws_smithy_runtime_api::client::auth::http::{ + HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID, HTTP_BEARER_AUTH_SCHEME_ID, + }; + + struct TestCase { + supported: Vec, + preference: Option, + expected_resolved_auths: Vec, + } + + for test_case in [ + TestCase { + supported: [HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID] + .map(AuthSchemeOption::from) + .to_vec(), + preference: None, + expected_resolved_auths: vec![ + HTTP_API_KEY_AUTH_SCHEME_ID, + HTTP_BASIC_AUTH_SCHEME_ID, + ], + }, + TestCase { + supported: [HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID] + .map(AuthSchemeOption::from) + .to_vec(), + preference: Some([].into()), + expected_resolved_auths: vec![ + HTTP_API_KEY_AUTH_SCHEME_ID, + HTTP_BASIC_AUTH_SCHEME_ID, + ], + }, + TestCase { + supported: [HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID] + .map(AuthSchemeOption::from) + .to_vec(), + preference: Some(["bogus"].map(AuthSchemeId::from).into()), + expected_resolved_auths: vec![ + HTTP_API_KEY_AUTH_SCHEME_ID, + HTTP_BASIC_AUTH_SCHEME_ID, + ], + }, + TestCase { + supported: [HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID] + .map(AuthSchemeOption::from) + .to_vec(), + preference: Some([HTTP_BASIC_AUTH_SCHEME_ID].into()), + expected_resolved_auths: vec![ + HTTP_BASIC_AUTH_SCHEME_ID, + HTTP_API_KEY_AUTH_SCHEME_ID, + ], + }, + TestCase { + supported: [HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID] + .map(AuthSchemeOption::from) + .to_vec(), + preference: Some([HTTP_BASIC_AUTH_SCHEME_ID, HTTP_API_KEY_AUTH_SCHEME_ID].into()), + expected_resolved_auths: vec![ + HTTP_BASIC_AUTH_SCHEME_ID, + HTTP_API_KEY_AUTH_SCHEME_ID, + ], + }, + TestCase { + supported: [HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID] + .map(AuthSchemeOption::from) + .to_vec(), + preference: Some( + [ + HTTP_BASIC_AUTH_SCHEME_ID, + HTTP_BEARER_AUTH_SCHEME_ID, + HTTP_API_KEY_AUTH_SCHEME_ID, + ] + .into(), + ), + expected_resolved_auths: vec![ + HTTP_BASIC_AUTH_SCHEME_ID, + HTTP_API_KEY_AUTH_SCHEME_ID, + ], + }, + ] { + let actual = reprioritize_with_auth_scheme_preference( + test_case.supported, + test_case.preference.as_ref(), + ) + .await; + let actual = actual + .iter() + .map(|opt| opt.scheme_id()) + .cloned() + .collect::>(); + assert_eq!(test_case.expected_resolved_auths, actual); + } + } } From fca5226853a0af3daf5733035f087130d7e7b318 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Tue, 15 Jul 2025 11:41:45 -0500 Subject: [PATCH 10/10] Align `AuthSchemeId` values with shape ID substrings This commit addresses https://github.com/smithy-lang/smithy-rs/pull/4203#discussion_r2202190808 --- .../auth_scheme_preference.rs | 21 +------------------ .../s3/tests/auth_scheme_preference.rs | 14 +++++++------ aws/sdk/integration-tests/s3/tests/express.rs | 7 ++++--- .../smithy/auth/AuthTypesGeneratorTest.kt | 4 ++-- .../src/client/auth/http.rs | 8 +++---- .../src/client/auth/no_auth.rs | 2 +- 6 files changed, 20 insertions(+), 36 deletions(-) diff --git a/aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs b/aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs index 0ff73a82842..2a5d765aac0 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/auth_scheme_preference.rs @@ -53,31 +53,12 @@ fn parse_auth_scheme_names(csv: &str) -> Result, _>>() .map(AuthSchemePreference::from) } -// This function is not automatically extensible. When a new scheme is introduced, -// it must be manually updated for the scheme to be recognized in the environment configuration. -// -// Furthermore, this function does not return a predefined `AuthSchemeId` like `HTTP_BASIC_AUTH_SCHEME_ID`. -// The predefined `SchemeId`s are gated behind the `http-auth` feature, but this function returns an `AuthSchemeId` -// that wraps the parsed string regardless of feature flags. If the feature is disabled, the returned -// auth scheme preference is simply ignored during auth scheme resolution. -fn runtime_api_auth_scheme_id(auth_scheme_name: &str) -> AuthSchemeId { - let runtime_api_auth_scheme_str = match auth_scheme_name { - "httpBasicAuth" => "http-basic-auth", - "httpDigestAuth" => "http-digest-auth", - "httpBearerAuth" => "http-bearer-auth", - "httpApiKeyAuth" => "http-api-key-auth", - "noAuth" => "no_auth", - otherwise => otherwise, - }; - AuthSchemeId::from(Cow::Owned(runtime_api_auth_scheme_str.to_owned())) -} - #[derive(Debug)] pub(crate) struct InvalidAuthSchemeNamesCsv { value: String, diff --git a/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs b/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs index f06c0bdf0ad..f05912be705 100644 --- a/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs +++ b/aws/sdk/integration-tests/s3/tests/auth_scheme_preference.rs @@ -31,9 +31,10 @@ async fn auth_scheme_preference_at_client_level_should_take_the_highest_priority .send() .await; - assert!(logs_contain( - "resolving identity scheme_id=AuthSchemeId { scheme_id: \"sigv4\" }" - )); + assert!(logs_contain(&format!( + "resolving identity scheme_id=AuthSchemeId {{ scheme_id: \"{auth_scheme_id_str}\" }}", + auth_scheme_id_str = aws_runtime::auth::sigv4::SCHEME_ID.inner(), + ))); } #[tracing_test::traced_test] @@ -60,7 +61,8 @@ async fn auth_scheme_preference_at_operation_level_should_take_the_highest_prior .send() .await; - assert!(logs_contain( - "resolving identity scheme_id=AuthSchemeId { scheme_id: \"sigv4\" }" - )); + assert!(logs_contain(&format!( + "resolving identity scheme_id=AuthSchemeId {{ scheme_id: \"{auth_scheme_id_str}\" }}", + auth_scheme_id_str = aws_runtime::auth::sigv4::SCHEME_ID.inner(), + ))); } diff --git a/aws/sdk/integration-tests/s3/tests/express.rs b/aws/sdk/integration-tests/s3/tests/express.rs index 1b5f5f7de62..b86547f955b 100644 --- a/aws/sdk/integration-tests/s3/tests/express.rs +++ b/aws/sdk/integration-tests/s3/tests/express.rs @@ -474,7 +474,8 @@ async fn no_auth_should_be_selected_when_no_credentials_is_configured() { .await ); - assert!(logs_contain( - "resolving identity scheme_id=AuthSchemeId { scheme_id: \"no_auth\" }" - )); + assert!(logs_contain(&format!( + "resolving identity scheme_id=AuthSchemeId {{ scheme_id: \"{auth_scheme_id_str}\" }}", + auth_scheme_id_str = aws_smithy_runtime::client::auth::no_auth::NO_AUTH_SCHEME_ID.inner(), + ))); } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt index 7f638d17d31..838f01c7ff5 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/auth/AuthTypesGeneratorTest.kt @@ -100,7 +100,7 @@ class AuthTypesGeneratorTest { .iter() .map(|opt| opt.scheme_id().inner()) .collect::>(); - assert_eq!(vec!["http-api-key-auth", "http-basic-auth"], actual); + assert_eq!(vec!["httpApiKeyAuth", "httpBasicAuth"], actual); """, *codegenScope, ) @@ -127,7 +127,7 @@ class AuthTypesGeneratorTest { .iter() .map(|opt| opt.scheme_id().inner()) .collect::>(); - assert_eq!(vec!["http-basic-auth", "http-bearer-auth"], actual); + assert_eq!(vec!["httpBasicAuth", "httpBearerAuth"], actual); """, *codegenScope, ) diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/auth/http.rs b/rust-runtime/aws-smithy-runtime-api/src/client/auth/http.rs index bb6ee4cc494..17e1db80e84 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/auth/http.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/auth/http.rs @@ -6,13 +6,13 @@ use crate::client::auth::AuthSchemeId; /// Auth scheme ID for HTTP API key based authentication. -pub const HTTP_API_KEY_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-api-key-auth"); +pub const HTTP_API_KEY_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("httpApiKeyAuth"); /// Auth scheme ID for HTTP Basic Auth. -pub const HTTP_BASIC_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-basic-auth"); +pub const HTTP_BASIC_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("httpBasicAuth"); /// Auth scheme ID for HTTP Bearer Auth. -pub const HTTP_BEARER_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-bearer-auth"); +pub const HTTP_BEARER_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("httpBearerAuth"); /// Auth scheme ID for HTTP Digest Auth. -pub const HTTP_DIGEST_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("http-digest-auth"); +pub const HTTP_DIGEST_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("httpDigestAuth"); diff --git a/rust-runtime/aws-smithy-runtime/src/client/auth/no_auth.rs b/rust-runtime/aws-smithy-runtime/src/client/auth/no_auth.rs index 195bbd75c86..f1972ccf1c2 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/auth/no_auth.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/auth/no_auth.rs @@ -20,7 +20,7 @@ use aws_smithy_types::config_bag::ConfigBag; use std::borrow::Cow; /// Auth scheme ID for "no auth". -pub const NO_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("no_auth"); +pub const NO_AUTH_SCHEME_ID: AuthSchemeId = AuthSchemeId::new("noAuth"); /// A [`RuntimePlugin`] that registers a "no auth" identity resolver and auth scheme. ///