Skip to content

Commit f5f1fd6

Browse files
sichanyooSichan Yoo
andauthored
feat: Bearer token auth (#786)
* Create bearer token signer, auth scheme, token identity, & identity resolver. * Add the new types (that got added to runtime) to codegen. * Add bearerTokenIdentityResolver field to client config, codegen it in client inits, codegen it in middleware context build process, and default it to StaticBearerTokenIdentityResolver in smithy-swift. * Add AuthUtils, extensible in AWS SDK via function override that adds more auth schemes to generated list. * BearerTokenSigner added to SmithyHTTPAuth module needs SdkHttpRequestBuilder from SmithyHTTPAPI module. * ktlint & swiftlint. * Add public initializer for BearerTokenIdentity. * Fix constructor for default value of bearerTokenIdentityResolver config property. * Fix auth scheme list codegen * Override on AWS side must not override the type indicated at declaration. * Fix typo * Add doc comments for new customer facing interface. Ktlint. * Add test for BearerTokenSigner * swiftlint * Fix broken codegen tests. * Remove unnecessary special handling for bearerTokenIdentityResolver in codegen. * ktlint * Update with Josh's HTTP type name changes. * Resolve PR comments --------- Co-authored-by: Sichan Yoo <[email protected]>
1 parent 5858bf1 commit f5f1fd6

File tree

16 files changed

+230
-5
lines changed

16 files changed

+230
-5
lines changed

Package.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ let package = Package(
162162
name: "SmithyHTTPAuth",
163163
dependencies: [
164164
"Smithy",
165+
"SmithyHTTPAPI",
165166
"SmithyHTTPAuthAPI",
166167
"SmithyIdentity",
167168
"SmithyIdentityAPI",
@@ -235,6 +236,10 @@ let package = Package(
235236
name: "SmithyXMLTests",
236237
dependencies: ["SmithyXML", "ClientRuntime"]
237238
),
239+
.testTarget(
240+
name: "SmithyHTTPAuthTests",
241+
dependencies: ["SmithyHTTPAuth", "SmithyHTTPAPI", "Smithy", "SmithyIdentity", "ClientRuntime"]
242+
),
238243
.testTarget(
239244
name: "SmithyJSONTests",
240245
dependencies: ["SmithyJSON", "ClientRuntime"]

Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import protocol SmithyHTTPAPI.HTTPClient
99
import protocol SmithyHTTPAuthAPI.AuthScheme
1010
import protocol SmithyHTTPAuthAPI.AuthSchemeResolver
11+
import protocol SmithyIdentity.BearerTokenIdentityResolver
1112

1213
public protocol DefaultHttpClientConfiguration: ClientConfiguration {
1314

@@ -30,6 +31,11 @@ public protocol DefaultHttpClientConfiguration: ClientConfiguration {
3031
/// Defaults to a auth scheme resolver generated based on Smithy service model.
3132
var authSchemeResolver: AuthSchemeResolver { get set }
3233

34+
/// The token identity resolver to be used for bearer token authentication.
35+
///
36+
/// If no resolver is supplied, the SDK will look for token in the `~/.aws/sso/cache` directory.
37+
var bearerTokenIdentityResolver: any BearerTokenIdentityResolver { get set }
38+
3339
/// Add an `HttpInterceptorProvider` that will be used to provide interceptors for all HTTP operations.
3440
///
3541
/// - Parameter provider: The `HttpInterceptorProvider` to add.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 class Smithy.Context
9+
import protocol SmithyHTTPAuthAPI.AuthScheme
10+
import protocol SmithyHTTPAuthAPI.Signer
11+
import struct Smithy.Attributes
12+
13+
public struct BearerTokenAuthScheme: AuthScheme {
14+
public let schemeID: String = "smithy.api#httpBearerAuth"
15+
public var signer: Signer = BearerTokenSigner()
16+
17+
public init() {}
18+
19+
public func customizeSigningProperties(
20+
signingProperties: Smithy.Attributes,
21+
context: Smithy.Context
22+
) throws -> Smithy.Attributes {
23+
// no-op
24+
return signingProperties
25+
}
26+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 class SmithyHTTPAPI.HTTPRequestBuilder
9+
import enum Smithy.ClientError
10+
import protocol SmithyIdentityAPI.Identity
11+
import protocol SmithyHTTPAuthAPI.Signer
12+
import struct Smithy.Attributes
13+
import struct SmithyIdentity.BearerTokenIdentity
14+
15+
/// The signer for HTTP bearer auth.
16+
/// Adds the Authorization header to the request using the resolved bearer token identity as its value.
17+
public class BearerTokenSigner: Signer {
18+
public init() {}
19+
20+
public func signRequest<IdentityT>(
21+
requestBuilder: SmithyHTTPAPI.HTTPRequestBuilder,
22+
identity: IdentityT,
23+
signingProperties: Smithy.Attributes
24+
) async throws -> SmithyHTTPAPI.HTTPRequestBuilder where IdentityT: SmithyIdentityAPI.Identity {
25+
guard let identity = identity as? BearerTokenIdentity else {
26+
throw Smithy.ClientError.authError(
27+
"Identity passed to the BearerTokenSigner must be of type BearerTokenIdentity."
28+
)
29+
}
30+
return requestBuilder.withHeader(name: "Authorization", value: "Bearer \(identity.token)")
31+
}
32+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 protocol SmithyIdentityAPI.Identity
9+
import struct Foundation.Date
10+
11+
/// The type representing bearer token identity, used in HTTP bearer auth.
12+
public struct BearerTokenIdentity: Identity {
13+
public let token: String
14+
public let expiration: Date?
15+
16+
public init(token: String, expiration: Date? = nil) {
17+
self.token = token
18+
self.expiration = expiration
19+
}
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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 protocol SmithyIdentityAPI.IdentityResolver
9+
10+
/// The type that resolves a bearer token identity for authenticating with a service.
11+
/// All concrete implementations for bearer token identity resolver must conform to this protocol.
12+
public protocol BearerTokenIdentityResolver: IdentityResolver where IdentityT == BearerTokenIdentity {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 struct Smithy.Attributes
9+
10+
/// The token identity resolver that returns a static token identity given to it at initialization.
11+
public struct StaticBearerTokenIdentityResolver: BearerTokenIdentityResolver {
12+
private let token: BearerTokenIdentity
13+
14+
public init(token: BearerTokenIdentity) {
15+
self.token = token
16+
}
17+
18+
public func getIdentity(identityProperties: Smithy.Attributes?) async throws -> BearerTokenIdentity {
19+
return token
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 class SmithyHTTPAuth.BearerTokenSigner
9+
import class SmithyHTTPAPI.HTTPRequestBuilder
10+
import struct Smithy.Attributes
11+
import struct SmithyIdentity.BearerTokenIdentity
12+
import XCTest
13+
14+
class BearerTokenSignerTests: XCTestCase {
15+
func testSignRequest() async throws {
16+
let tokenIdentity = BearerTokenIdentity(token: "dummy-token-for-test")
17+
let unsignedRequest = HTTPRequestBuilder()
18+
let signer = BearerTokenSigner()
19+
let signedRequest = try await signer.signRequest(requestBuilder: unsignedRequest, identity: tokenIdentity, signingProperties: Attributes())
20+
XCTAssert(signedRequest.headers.exists(name: "Authorization"))
21+
XCTAssertEqual(signedRequest.headers.value(for: "Authorization"), "Bearer \(tokenIdentity.token)")
22+
}
23+
}

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ class SwiftDependency(
130130
"smithy-swift",
131131
DistributionMethod.SPR,
132132
)
133+
val SMITHY_HTTP_AUTH = SwiftDependency(
134+
"SmithyHTTPAuth",
135+
"main",
136+
"0.0.1",
137+
"aws-sdk-swift",
138+
"../../../smithy-swift",
139+
"smithy-swift",
140+
DistributionMethod.SPR,
141+
)
133142
val SMITHY_CHECKSUMS_API = SwiftDependency(
134143
"SmithyChecksumsAPI",
135144
"main",

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultHttpClientConfiguration.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
1010
import software.amazon.smithy.swift.codegen.lang.AccessModifier
1111
import software.amazon.smithy.swift.codegen.lang.Function
1212
import software.amazon.smithy.swift.codegen.lang.FunctionParameter
13+
import software.amazon.smithy.swift.codegen.model.toGeneric
1314
import software.amazon.smithy.swift.codegen.model.toOptional
1415
import software.amazon.smithy.swift.codegen.swiftmodules.ClientRuntimeTypes
1516
import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes
1617
import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAuthAPITypes
18+
import software.amazon.smithy.swift.codegen.swiftmodules.SmithyIdentityTypes
19+
import software.amazon.smithy.swift.codegen.utils.AuthUtils
1720

1821
class DefaultHttpClientConfiguration : ClientConfiguration {
1922
override val swiftProtocolName: Symbol = ClientRuntimeTypes.Core.DefaultHttpClientConfiguration
@@ -33,7 +36,7 @@ class DefaultHttpClientConfiguration : ClientConfiguration {
3336
ClientRuntimeTypes.Http.HttpClientConfiguration,
3437
{ it.format("\$N.defaultHttpClientConfiguration", ClientRuntimeTypes.Core.ClientConfigurationDefaults) },
3538
),
36-
ConfigProperty("authSchemes", SmithyHTTPAuthAPITypes.AuthSchemes.toOptional()),
39+
ConfigProperty("authSchemes", SmithyHTTPAuthAPITypes.AuthSchemes.toOptional(), AuthUtils(ctx).authSchemesDefaultProvider),
3740
ConfigProperty(
3841
"authSchemeResolver",
3942
SmithyHTTPAuthAPITypes.AuthSchemeResolver,
@@ -44,6 +47,11 @@ class DefaultHttpClientConfiguration : ClientConfiguration {
4447
ClientRuntimeTypes.Core.HttpInterceptorProviders,
4548
{ "[]" },
4649
accessModifier = AccessModifier.PublicPrivateSet
50+
),
51+
ConfigProperty(
52+
"bearerTokenIdentityResolver",
53+
SmithyIdentityTypes.BearerTokenIdentityResolver.toGeneric(),
54+
{ it.format("\$N(token: \$N(token: \"\"))", SmithyIdentityTypes.StaticBearerTokenIdentityResolver, SmithyIdentityTypes.BearerTokenIdentity) }
4755
)
4856
)
4957

0 commit comments

Comments
 (0)