Skip to content

Commit f811e02

Browse files
authored
feat: Support auth scheme preference list (#1953)
1 parent 3250ce4 commit f811e02

File tree

9 files changed

+256
-2
lines changed

9 files changed

+256
-2
lines changed

Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/AWSClientConfigDefaultsProvider.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ public class AWSClientConfigDefaultsProvider: ClientConfigDefaultsProvider {
4141
return resolvedAppID
4242
}
4343

44+
public static func authSchemePreference(_ preference: String? = nil) throws -> [String]? {
45+
let fileBasedConfig = try CRTFileBasedConfiguration.make()
46+
let resolvedPreference: [String]?
47+
if let authSchemePreference = preference {
48+
resolvedPreference = AuthSchemeConfig.normalizeSchemes(authSchemePreference)
49+
} else {
50+
resolvedPreference = AuthSchemeConfig.authSchemePreference(
51+
configValue: nil,
52+
profileName: nil,
53+
fileBasedConfig: fileBasedConfig
54+
)
55+
}
56+
return resolvedPreference
57+
}
58+
4459
public static func requestChecksumCalculation(
4560
_ requestChecksumCalculation: AWSChecksumCalculationMode? = nil
4661
) throws -> AWSChecksumCalculationMode {

Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSDefaultClientConfiguration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public protocol AWSDefaultClientConfiguration {
1515
/// If no resolver is supplied, `AWSSDKIdentity.DefaultAWSCredentialIdentityResolverChain` gets used by default.
1616
var awsCredentialIdentityResolver: any AWSCredentialIdentityResolver { get set }
1717

18+
var authSchemePreference: [String]? { get set }
19+
1820
/// Specifies whether FIPS endpoints should be used.
1921
var useFIPS: Bool? { get set }
2022

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
@_spi(FileBasedConfig) import AWSSDKCommon
9+
10+
enum AuthSchemeConfig {
11+
12+
/// Determines the auth scheme prioritization to be used from the given config, if any.
13+
/// - Parameters:
14+
/// - configValue: The auth scheme priority list passed at client construction, or `nil` if none was passed.
15+
/// - profileName: The profile name passed at client construction. If `nil` is passed, the SDK will resolve the profile to be used.
16+
/// - fileBasedConfig: The file-based config from which to load configuration, if needed.
17+
/// - Returns: The auth scheme priority that was resolved, or `nil` if none was resolved.
18+
static func authSchemePreference(
19+
configValue: [String]?,
20+
profileName: String?,
21+
fileBasedConfig: FileBasedConfiguration
22+
) -> [String]? {
23+
return FieldResolver(
24+
configValue: configValue,
25+
envVarName: "AWS_AUTH_SCHEME_PREFERENCE",
26+
configFieldName: "auth_scheme_preference",
27+
fileBasedConfig: fileBasedConfig,
28+
profileName: profileName,
29+
converter: { value in
30+
let normalized = normalizeSchemes(value)
31+
return normalized.isEmpty ? nil : normalized
32+
}
33+
).value
34+
}
35+
36+
static func normalizeSchemes(_ input: String) -> [String] {
37+
return input
38+
.split(separator: ",")
39+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
40+
.filter { !$0.isEmpty }
41+
}
42+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
import XCTest
10+
@testable @_spi(FileBasedConfig) import AWSClientRuntime
11+
@testable @_spi(FileBasedConfig) import AWSSDKCommon
12+
13+
class AuthSchemeConfigTests: XCTestCase {
14+
let envVarName = "AWS_AUTH_SCHEME_PREFERENCE"
15+
let configFieldName = "auth_scheme_preference"
16+
var fileBasedConfig: FileBasedConfiguration!
17+
18+
override func setUp() {
19+
super.setUp()
20+
fileBasedConfig = try! CRTFileBasedConfiguration(
21+
configFilePath: Bundle.module.path(forResource: "auth_scheme_config_tests", ofType: nil)!,
22+
credentialsFilePath: nil
23+
)
24+
}
25+
26+
override func tearDown() {
27+
unsetenv(envVarName)
28+
super.tearDown()
29+
}
30+
31+
// authSchemePreference Tests
32+
33+
func test_authSchemePreference_returnsConfigValueWhenProvided() {
34+
let expected = ["sigv4", "sigv4a"]
35+
let result = AuthSchemeConfig.authSchemePreference(
36+
configValue: expected,
37+
profileName: nil,
38+
fileBasedConfig: fileBasedConfig
39+
)
40+
XCTAssertEqual(result, ["sigv4", "sigv4a"])
41+
}
42+
43+
func test_authSchemePreference_returnsNilWhenConfigValueIsEmpty() {
44+
let result = AuthSchemeConfig.authSchemePreference(
45+
configValue: [],
46+
profileName: nil,
47+
fileBasedConfig: fileBasedConfig
48+
)
49+
XCTAssertEqual(result, [])
50+
}
51+
52+
func test_authSchemePreference_returnsEnvironmentValueWhenNoConfigValue() {
53+
let expected = "sigv4a,sigv4"
54+
setenv(envVarName, expected, 1)
55+
56+
let result = AuthSchemeConfig.authSchemePreference(
57+
configValue: nil,
58+
profileName: nil,
59+
fileBasedConfig: fileBasedConfig
60+
)
61+
XCTAssertEqual(result, ["sigv4a", "sigv4"])
62+
}
63+
64+
func test_authSchemePreference_returnsNilWhenEnvironmentValueIsEmpty() {
65+
setenv(envVarName, "", 1)
66+
67+
let result = AuthSchemeConfig.authSchemePreference(
68+
configValue: nil,
69+
profileName: nil,
70+
fileBasedConfig: fileBasedConfig
71+
)
72+
// When env var is empty, FieldResolver falls through to file config
73+
// which has "sigv4" in the default profile
74+
XCTAssertEqual(result, ["sigv4"])
75+
}
76+
77+
func test_authSchemePreference_returnsFileConfigValueFromDefaultProfile() {
78+
// Assuming the test config file has auth_scheme_preference = "sigv4" in default profile
79+
let result = AuthSchemeConfig.authSchemePreference(
80+
configValue: nil,
81+
profileName: nil,
82+
fileBasedConfig: fileBasedConfig
83+
)
84+
XCTAssertEqual(result, ["sigv4"])
85+
}
86+
87+
func test_authSchemePreference_returnsFileConfigValueFromSpecifiedProfile() {
88+
let result = AuthSchemeConfig.authSchemePreference(
89+
configValue: nil,
90+
profileName: "alt-profile",
91+
fileBasedConfig: fileBasedConfig
92+
)
93+
XCTAssertEqual(result, ["sigv4a", "sigv4"])
94+
}
95+
96+
func test_authSchemePreference_returnsNilWhenNoSourceIsSet() {
97+
let result = AuthSchemeConfig.authSchemePreference(
98+
configValue: nil,
99+
profileName: "non-existent-profile",
100+
fileBasedConfig: fileBasedConfig
101+
)
102+
XCTAssertNil(result)
103+
}
104+
105+
// normalizeSchemes Tests
106+
107+
func test_normalizeSchemes_handlesSingleScheme() {
108+
let result = AuthSchemeConfig.normalizeSchemes("sigv4")
109+
XCTAssertEqual(result, ["sigv4"])
110+
}
111+
112+
func test_normalizeSchemes_handlesMultipleSchemes() {
113+
let result = AuthSchemeConfig.normalizeSchemes("sigv4,sigv4a,bearer")
114+
XCTAssertEqual(result, ["sigv4", "sigv4a", "bearer"])
115+
}
116+
117+
func test_normalizeSchemes_removesSpaces() {
118+
let result = AuthSchemeConfig.normalizeSchemes("sigv4, sigv4a , bearer")
119+
XCTAssertEqual(result, ["sigv4", "sigv4a", "bearer"])
120+
}
121+
122+
func test_normalizeSchemes_removesTabs() {
123+
let result = AuthSchemeConfig.normalizeSchemes("sigv4,\tsigv4a\t,\tbearer")
124+
XCTAssertEqual(result, ["sigv4", "sigv4a", "bearer"])
125+
}
126+
127+
func test_normalizeSchemes_removesMultipleSpacesAndTabs() {
128+
let result = AuthSchemeConfig.normalizeSchemes("sigv4 , \t sigv4a\t\t, bearer ")
129+
XCTAssertEqual(result, ["sigv4", "sigv4a", "bearer"])
130+
}
131+
132+
func test_normalizeSchemes_handlesEmptyElements() {
133+
let result = AuthSchemeConfig.normalizeSchemes("sigv4,,sigv4a,,,bearer,")
134+
XCTAssertEqual(result, ["sigv4", "sigv4a", "bearer"])
135+
}
136+
137+
func test_normalizeSchemes_handlesEmptyString() {
138+
let result = AuthSchemeConfig.normalizeSchemes("")
139+
XCTAssertEqual(result, [])
140+
}
141+
142+
func test_normalizeSchemes_handlesOnlyCommas() {
143+
let result = AuthSchemeConfig.normalizeSchemes(",,,")
144+
XCTAssertEqual(result, [])
145+
}
146+
147+
func test_normalizeSchemes_handlesOnlyWhitespace() {
148+
let result = AuthSchemeConfig.normalizeSchemes(" \t ")
149+
XCTAssertEqual(result, [])
150+
}
151+
152+
// authSchemePreference Tests with environment and config
153+
154+
func test_authSchemePreference_normalizesEnvironmentValue() {
155+
setenv(envVarName, "sigv4 , sigv4a", 1)
156+
157+
let result = AuthSchemeConfig.authSchemePreference(
158+
configValue: nil,
159+
profileName: nil,
160+
fileBasedConfig: fileBasedConfig
161+
)
162+
XCTAssertEqual(result, ["sigv4", "sigv4a"])
163+
}
164+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[default]
2+
auth_scheme_preference = sigv4
3+
4+
[profile alt-profile]
5+
auth_scheme_preference = sigv4a,sigv4

codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/plugins/AuthSchemePlugin.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import software.amazon.smithy.swift.codegen.model.toOptional
1212
import software.amazon.smithy.swift.codegen.swiftmodules.ClientRuntimeTypes
1313
import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAuthAPITypes
1414
import software.amazon.smithy.swift.codegen.swiftmodules.SmithyIdentityTypes
15+
import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes
1516
import software.amazon.smithy.swift.codegen.utils.toUpperCamelCase
1617

1718
class AuthSchemePlugin(
@@ -31,6 +32,7 @@ class AuthSchemePlugin(
3132
) {
3233
writer.openBlock("public class $pluginName: \$N {", "}", ClientRuntimeTypes.Core.Plugin) {
3334
writer.write("private var authSchemes: \$N", SmithyHTTPAuthAPITypes.AuthSchemes.toOptional())
35+
writer.write("private var authSchemePreference: \$N", SwiftTypes.StringList)
3436
writer.write("private var authSchemeResolver: \$N", SmithyHTTPAuthAPITypes.AuthSchemeResolver.toOptional())
3537
writer.write(
3638
"private var awsCredentialIdentityResolver: \$N",
@@ -43,15 +45,17 @@ class AuthSchemePlugin(
4345

4446
writer.write("")
4547
writer.openBlock(
46-
"public init(authSchemes: \$N = nil, authSchemeResolver: \$N = nil, awsCredentialIdentityResolver: \$N = nil, bearerTokenIdentityResolver: \$N = nil) {",
48+
"public init(authSchemes: \$N = nil, authSchemePreference: \$N = nil, authSchemeResolver: \$N = nil, awsCredentialIdentityResolver: \$N = nil, bearerTokenIdentityResolver: \$N = nil) {",
4749
"}",
4850
SmithyHTTPAuthAPITypes.AuthSchemes.toOptional(),
51+
SwiftTypes.StringList.toOptional(),
4952
AuthSchemeResolverGenerator.getServiceSpecificAuthSchemeResolverName(ctx).toOptional(),
5053
SmithyIdentityTypes.AWSCredentialIdentityResolver.toGeneric().toOptional(),
5154
SmithyIdentityTypes.BearerTokenIdentityResolver.toGeneric().toOptional(),
5255
) {
5356
writer.write("self.authSchemeResolver = authSchemeResolver")
5457
writer.write("self.authSchemes = authSchemes")
58+
writer.write("self.authSchemePreference = authSchemePreference ?? []")
5559
writer.write("self.awsCredentialIdentityResolver = awsCredentialIdentityResolver")
5660
writer.write("self.bearerTokenIdentityResolver = bearerTokenIdentityResolver")
5761
}
@@ -65,6 +69,9 @@ class AuthSchemePlugin(
6569
writer.openBlock("if (self.authSchemes != nil) {", "}") {
6670
writer.write("config.authSchemes = self.authSchemes")
6771
}
72+
writer.openBlock("if (self.authSchemePreference != nil) {", "}") {
73+
writer.write("config.authSchemePreference = self.authSchemePreference")
74+
}
6875
writer.openBlock("if (self.authSchemeResolver != nil) {", "}") {
6976
writer.write("config.authSchemeResolver = self.authSchemeResolver!")
7077
}

codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extension GetFooInput {
2828
.withLogger(value: config.logger)
2929
.withPartitionID(value: config.partitionID)
3030
.withAuthSchemes(value: config.authSchemes ?? [])
31+
.withAuthSchemePreference(value: config.authSchemePreference)
3132
.withAuthSchemeResolver(value: config.authSchemeResolver)
3233
.withUnsignedPayloadTrait(value: false)
3334
.withSocketTimeout(value: config.httpClientConfiguration.socketTimeout)
@@ -102,6 +103,7 @@ extension PostFooInput {
102103
.withLogger(value: config.logger)
103104
.withPartitionID(value: config.partitionID)
104105
.withAuthSchemes(value: config.authSchemes ?? [])
106+
.withAuthSchemePreference(value: config.authSchemePreference)
105107
.withAuthSchemeResolver(value: config.authSchemeResolver)
106108
.withUnsignedPayloadTrait(value: false)
107109
.withSocketTimeout(value: config.httpClientConfiguration.socketTimeout)
@@ -179,6 +181,7 @@ extension PutFooInput {
179181
.withLogger(value: config.logger)
180182
.withPartitionID(value: config.partitionID)
181183
.withAuthSchemes(value: config.authSchemes ?? [])
184+
.withAuthSchemePreference(value: config.authSchemePreference)
182185
.withAuthSchemeResolver(value: config.authSchemeResolver)
183186
.withUnsignedPayloadTrait(value: false)
184187
.withSocketTimeout(value: config.httpClientConfiguration.socketTimeout)
@@ -256,6 +259,7 @@ extension PutObjectInput {
256259
.withLogger(value: config.logger)
257260
.withPartitionID(value: config.partitionID)
258261
.withAuthSchemes(value: config.authSchemes ?? [])
262+
.withAuthSchemePreference(value: config.authSchemePreference)
259263
.withAuthSchemeResolver(value: config.authSchemeResolver)
260264
.withUnsignedPayloadTrait(value: false)
261265
.withSocketTimeout(value: config.httpClientConfiguration.socketTimeout)

codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1ProtocolGeneratorTests.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ extension ExampleClient {
104104
public var httpClientEngine: SmithyHTTPAPI.HTTPClient
105105
public var httpClientConfiguration: ClientRuntime.HttpClientConfiguration
106106
public var authSchemes: SmithyHTTPAuthAPI.AuthSchemes?
107+
public var authSchemePreference: [String]?
107108
public var authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver
108109
public var bearerTokenIdentityResolver: any SmithyIdentity.BearerTokenIdentityResolver
109110
public private(set) var interceptorProviders: [ClientRuntime.InterceptorProvider]
@@ -131,6 +132,7 @@ extension ExampleClient {
131132
_ httpClientEngine: SmithyHTTPAPI.HTTPClient,
132133
_ httpClientConfiguration: ClientRuntime.HttpClientConfiguration,
133134
_ authSchemes: SmithyHTTPAuthAPI.AuthSchemes?,
135+
_ authSchemePreference: [String]?,
134136
_ authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver,
135137
_ bearerTokenIdentityResolver: any SmithyIdentity.BearerTokenIdentityResolver,
136138
_ interceptorProviders: [ClientRuntime.InterceptorProvider],
@@ -156,6 +158,7 @@ extension ExampleClient {
156158
self.httpClientEngine = httpClientEngine
157159
self.httpClientConfiguration = httpClientConfiguration
158160
self.authSchemes = authSchemes
161+
self.authSchemePreference = authSchemePreference
159162
self.authSchemeResolver = authSchemeResolver
160163
self.bearerTokenIdentityResolver = bearerTokenIdentityResolver
161164
self.interceptorProviders = interceptorProviders
@@ -184,6 +187,7 @@ extension ExampleClient {
184187
httpClientEngine: SmithyHTTPAPI.HTTPClient? = nil,
185188
httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil,
186189
authSchemes: SmithyHTTPAuthAPI.AuthSchemes? = nil,
190+
authSchemePreference: [String]? = nil,
187191
authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver? = nil,
188192
bearerTokenIdentityResolver: (any SmithyIdentity.BearerTokenIdentityResolver)? = nil,
189193
interceptorProviders: [ClientRuntime.InterceptorProvider]? = nil,
@@ -210,6 +214,7 @@ extension ExampleClient {
210214
httpClientEngine ?? AWSClientConfigDefaultsProvider.httpClientEngine(httpClientConfiguration),
211215
httpClientConfiguration ?? AWSClientConfigDefaultsProvider.httpClientConfiguration(),
212216
authSchemes ?? [AWSSDKHTTPAuth.SigV4AuthScheme()],
217+
authSchemePreference ?? nil,
213218
authSchemeResolver ?? DefaultExampleAuthSchemeResolver(),
214219
bearerTokenIdentityResolver ?? SmithyIdentity.StaticBearerTokenIdentityResolver(token: SmithyIdentity.BearerTokenIdentity(token: "")),
215220
interceptorProviders ?? [],
@@ -238,6 +243,7 @@ extension ExampleClient {
238243
httpClientEngine: SmithyHTTPAPI.HTTPClient? = nil,
239244
httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil,
240245
authSchemes: SmithyHTTPAuthAPI.AuthSchemes? = nil,
246+
authSchemePreference: [String]? = nil,
241247
authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver? = nil,
242248
bearerTokenIdentityResolver: (any SmithyIdentity.BearerTokenIdentityResolver)? = nil,
243249
interceptorProviders: [ClientRuntime.InterceptorProvider]? = nil,
@@ -264,6 +270,7 @@ extension ExampleClient {
264270
httpClientEngine ?? AWSClientConfigDefaultsProvider.httpClientEngine(httpClientConfiguration),
265271
httpClientConfiguration ?? AWSClientConfigDefaultsProvider.httpClientConfiguration(),
266272
authSchemes ?? [AWSSDKHTTPAuth.SigV4AuthScheme()],
273+
authSchemePreference ?? nil,
267274
authSchemeResolver ?? DefaultExampleAuthSchemeResolver(),
268275
bearerTokenIdentityResolver ?? SmithyIdentity.StaticBearerTokenIdentityResolver(token: SmithyIdentity.BearerTokenIdentity(token: "")),
269276
interceptorProviders ?? [],
@@ -293,6 +300,7 @@ extension ExampleClient {
293300
httpClientEngine: nil,
294301
httpClientConfiguration: nil,
295302
authSchemes: nil,
303+
authSchemePreference: nil,
296304
authSchemeResolver: nil,
297305
bearerTokenIdentityResolver: nil,
298306
interceptorProviders: nil,
@@ -322,6 +330,7 @@ extension ExampleClient {
322330
AWSClientConfigDefaultsProvider.httpClientEngine(),
323331
AWSClientConfigDefaultsProvider.httpClientConfiguration(),
324332
[AWSSDKHTTPAuth.SigV4AuthScheme()],
333+
nil,
325334
DefaultExampleAuthSchemeResolver(),
326335
SmithyIdentity.StaticBearerTokenIdentityResolver(token: SmithyIdentity.BearerTokenIdentity(token: "")),
327336
[],

0 commit comments

Comments
 (0)