Skip to content

Commit a2a7d7a

Browse files
authored
Add fallback equality on noAuth scheme ID (#4232)
## Motivation and Context Implements fallback equality for no auth `AuthSchemeId` so that `AuthSchemeId::from("no_auth")` (legacy) and `AuthSchemeId::from("noAuth")` (updated) should be treated as equivalent. ## Description In #4203, the internal raw strings of pre-defined `AuthSchemeId` were updated to better align with the Smithy spec ([discussion](#4203 (comment))). Acknowledging that this was a breaking change and that customers should not rely on these internal representations, we did receive reports of issues related to this update. After discussion, we proceeded with implementing fallback equality for no auth scheme ID to allow for a safer rollout. ## Testing - Added unit tests for manually implemented traits for `AuthSchemeId`, `PartialEq`, `Hash`, and `Ord` - Added an auth scheme preference test to verify the legacy `no_auth` is supported ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [x] For changes to the smithy-rs codegen or runtime crates, I have created a changelog entry Markdown file in the `.changelog` directory, specifying "client," "server," or both in the `applies_to` key. - [x] For changes to the AWS SDK, generated SDK code, or SDK runtime crates, I have created a changelog entry Markdown file in the `.changelog` directory, specifying "aws-sdk-rust" in the `applies_to` key. ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent f2ec135 commit a2a7d7a

File tree

5 files changed

+194
-4
lines changed

5 files changed

+194
-4
lines changed

.changelog/1753385864.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
applies_to:
3+
- aws-sdk-rust
4+
- client
5+
authors:
6+
- ysaito1001
7+
references:
8+
- smithy-rs#4232
9+
breaking: false
10+
new_feature: false
11+
bug_fix: true
12+
---
13+
Add fallback equality on no auth `AuthSchemeId` for backward compatibility, treating `AuthSchemeId::from("no_auth")` (legacy) and `AuthSchemeId::from("noAuth")` (updated) as equivalent.

aws/sdk/integration-tests/s3/tests/no_auth.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use aws_smithy_http_client::test_util::capture_request;
99
use aws_smithy_http_client::test_util::dvr::ReplayingClient;
1010
use aws_smithy_runtime::client::auth::no_auth::NO_AUTH_SCHEME_ID;
1111
use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
12+
use aws_smithy_runtime_api::client::auth::AuthSchemeId;
1213

1314
#[tokio::test]
1415
async fn list_objects() {
@@ -160,3 +161,30 @@ async fn no_auth_should_be_selected_when_no_credentials_is_configured() {
160161
auth_scheme_id_str = NO_AUTH_SCHEME_ID.inner(),
161162
)));
162163
}
164+
165+
#[tracing_test::traced_test]
166+
#[tokio::test]
167+
async fn auth_scheme_preference_specifying_legacy_no_auth_scheme_id_should_be_supported() {
168+
let (http_client, _) = capture_request(None);
169+
let conf = Config::builder()
170+
.http_client(http_client)
171+
.region(Region::new("us-east-2"))
172+
.with_test_defaults()
173+
.auth_scheme_preference([AuthSchemeId::from("no_auth")])
174+
.build();
175+
let client = Client::from_conf(conf);
176+
let _ = client
177+
.get_object()
178+
.bucket("arn:aws:s3::123456789012:accesspoint/mfzwi23gnjvgw.mrap")
179+
.key("doesnotmatter")
180+
.send()
181+
.await;
182+
183+
// What should appear in the log is the updated no auth scheme ID, not the legacy one.
184+
// The legacy no auth scheme ID passed to the auth scheme preference merely reprioritizes
185+
// ones supported in the runtime, which should contain the updated no auth scheme ID.
186+
assert!(logs_contain(&format!(
187+
"resolving identity scheme_id=AuthSchemeId {{ scheme_id: \"{auth_scheme_id_str}\" }}",
188+
auth_scheme_id_str = NO_AUTH_SCHEME_ID.inner(),
189+
)));
190+
}

rust-runtime/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-runtime/aws-smithy-runtime-api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "aws-smithy-runtime-api"
3-
version = "1.8.4"
3+
version = "1.8.5"
44
authors = ["AWS Rust SDK Team <[email protected]>", "Zelda Hessler <[email protected]>"]
55
description = "Smithy runtime types."
66
edition = "2021"

rust-runtime/aws-smithy-runtime-api/src/client/auth.rs

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,22 @@ impl AsRef<AuthSchemeId> for AuthSchemeId {
151151
}
152152
}
153153

154+
// Normalizes auth scheme IDs for comparison and hashing by treating "no_auth" and "noAuth" as equivalent
155+
// by converting "no_auth" to "noAuth".
156+
// This is for backward compatibility; "no_auth" was incorrectly used in earlier GA versions of the SDK and
157+
// could be used still in some places.
158+
const fn normalize_auth_scheme_id(s: &'static str) -> &'static str {
159+
match s.as_bytes() {
160+
b"no_auth" => "noAuth",
161+
_ => s,
162+
}
163+
}
164+
154165
impl AuthSchemeId {
155166
/// Creates a new auth scheme ID.
156167
pub const fn new(scheme_id: &'static str) -> Self {
157168
Self {
158-
scheme_id: Cow::Borrowed(scheme_id),
169+
scheme_id: Cow::Borrowed(normalize_auth_scheme_id(scheme_id)),
159170
}
160171
}
161172

@@ -188,7 +199,19 @@ impl From<&'static str> for AuthSchemeId {
188199

189200
impl From<Cow<'static, str>> for AuthSchemeId {
190201
fn from(scheme_id: Cow<'static, str>) -> Self {
191-
Self { scheme_id }
202+
let normalized_scheme_id = match &scheme_id {
203+
Cow::Borrowed(s) => Cow::Borrowed(normalize_auth_scheme_id(s)),
204+
Cow::Owned(s) => {
205+
if s == "no_auth" {
206+
Cow::Borrowed("noAuth")
207+
} else {
208+
scheme_id
209+
}
210+
}
211+
};
212+
Self {
213+
scheme_id: normalized_scheme_id,
214+
}
192215
}
193216
}
194217

@@ -454,3 +477,129 @@ where
454477
}
455478
}
456479
}
480+
481+
#[cfg(test)]
482+
mod tests {
483+
use super::*;
484+
485+
#[test]
486+
fn test_auth_scheme_id_new_normalizes_no_auth() {
487+
// Test that "no_auth" gets normalized to "noAuth"
488+
let auth_scheme_id = AuthSchemeId::new("no_auth");
489+
assert_eq!(auth_scheme_id.inner(), "noAuth");
490+
}
491+
492+
#[test]
493+
fn test_auth_scheme_id_new_preserves_no_auth_camel_case() {
494+
// Test that "noAuth" remains unchanged
495+
let auth_scheme_id = AuthSchemeId::new("noAuth");
496+
assert_eq!(auth_scheme_id.inner(), "noAuth");
497+
}
498+
499+
#[test]
500+
fn test_auth_scheme_id_new_preserves_other_schemes() {
501+
// Test that other auth scheme IDs are not modified
502+
let test_cases = [
503+
"sigv4",
504+
"sigv4a",
505+
"httpBearerAuth",
506+
"httpBasicAuth",
507+
"custom_auth",
508+
"bearer",
509+
"basic",
510+
];
511+
512+
for scheme in test_cases {
513+
let auth_scheme_id = AuthSchemeId::new(scheme);
514+
assert_eq!(auth_scheme_id.inner(), scheme);
515+
}
516+
}
517+
518+
#[test]
519+
fn test_auth_scheme_id_equality_after_normalization() {
520+
// Test that "no_auth" and "noAuth" are considered equal after normalization
521+
let no_auth_underscore = AuthSchemeId::new("no_auth");
522+
let no_auth_camel = AuthSchemeId::new("noAuth");
523+
524+
assert_eq!(no_auth_underscore, no_auth_camel);
525+
assert_eq!(no_auth_underscore.inner(), no_auth_camel.inner());
526+
}
527+
528+
#[test]
529+
fn test_auth_scheme_id_hash_consistency_after_normalization() {
530+
use std::collections::HashMap;
531+
532+
// Test that normalized IDs have consistent hashing behavior
533+
let mut map = HashMap::new();
534+
let no_auth_underscore = AuthSchemeId::new("no_auth");
535+
let no_auth_camel = AuthSchemeId::new("noAuth");
536+
537+
map.insert(no_auth_underscore.clone(), "value1");
538+
map.insert(no_auth_camel.clone(), "value2");
539+
540+
// Should only have one entry since they normalize to the same value
541+
assert_eq!(map.len(), 1);
542+
assert_eq!(map.get(&no_auth_underscore), Some(&"value2"));
543+
assert_eq!(map.get(&no_auth_camel), Some(&"value2"));
544+
}
545+
546+
#[test]
547+
fn test_auth_scheme_id_ordering_after_normalization() {
548+
// Test that ordering works correctly with normalized values
549+
let no_auth_underscore = AuthSchemeId::new("no_auth");
550+
let no_auth_camel = AuthSchemeId::new("noAuth");
551+
let other_scheme = AuthSchemeId::new("sigv4");
552+
553+
assert_eq!(
554+
no_auth_underscore.cmp(&no_auth_camel),
555+
std::cmp::Ordering::Equal
556+
);
557+
assert_eq!(no_auth_underscore.cmp(&other_scheme), "noAuth".cmp("sigv4"));
558+
}
559+
560+
#[test]
561+
fn test_normalize_auth_scheme_id_function() {
562+
// Test the normalize function directly
563+
assert_eq!(normalize_auth_scheme_id("no_auth"), "noAuth");
564+
assert_eq!(normalize_auth_scheme_id("noAuth"), "noAuth");
565+
assert_eq!(normalize_auth_scheme_id("sigv4"), "sigv4");
566+
assert_eq!(normalize_auth_scheme_id("custom"), "custom");
567+
}
568+
569+
#[test]
570+
fn test_auth_scheme_id_from_cow_borrowed_normalizes_no_auth() {
571+
// Test that Cow::Borrowed("no_auth") gets normalized to "noAuth"
572+
let auth_scheme_id = AuthSchemeId::from(Cow::Borrowed("no_auth"));
573+
assert_eq!(auth_scheme_id.inner(), "noAuth");
574+
}
575+
576+
#[test]
577+
fn test_auth_scheme_id_from_cow_borrowed_preserves_no_auth_camel_case() {
578+
// Test that Cow::Borrowed("noAuth") remains unchanged
579+
let auth_scheme_id = AuthSchemeId::from(Cow::Borrowed("noAuth"));
580+
assert_eq!(auth_scheme_id.inner(), "noAuth");
581+
}
582+
583+
#[test]
584+
fn test_auth_scheme_id_from_cow_owned_normalizes_no_auth() {
585+
// Test that Cow::Owned(String::from("no_auth")) gets normalized to "noAuth"
586+
let auth_scheme_id = AuthSchemeId::from(Cow::Owned(String::from("no_auth")));
587+
assert_eq!(auth_scheme_id.inner(), "noAuth");
588+
}
589+
590+
#[test]
591+
fn test_auth_scheme_id_from_cow_owned_preserves_no_auth_camel_case() {
592+
// Test that Cow::Owned(String::from("noAuth")) remains unchanged
593+
let auth_scheme_id = AuthSchemeId::from(Cow::Owned(String::from("noAuth")));
594+
assert_eq!(auth_scheme_id.inner(), "noAuth");
595+
}
596+
597+
#[test]
598+
fn test_auth_scheme_id_from_cow_between_borrowed_and_owned_mixing_updated_and_legacy() {
599+
let borrowed_no_auth = AuthSchemeId::from(Cow::Borrowed("noAuth"));
600+
let owned_no_auth = AuthSchemeId::from(Cow::Owned(String::from("no_auth")));
601+
602+
assert_eq!(borrowed_no_auth, owned_no_auth);
603+
assert_eq!(borrowed_no_auth.inner(), owned_no_auth.inner());
604+
}
605+
}

0 commit comments

Comments
 (0)