Skip to content

Commit 0409317

Browse files
authored
feat: enable auth resolution via endpoints (#993)
1 parent ad34f2a commit 0409317

File tree

29 files changed

+482
-146
lines changed

29 files changed

+482
-146
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "0be03245-2d3e-4bac-8bea-f893b29b6539",
3+
"type": "feature",
4+
"description": "Enable resolving auth schemes via endpoint resolution"
5+
}

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ buildscript {
1515
// Add our custom gradle plugin(s) to buildscript classpath (comes from github source)
1616
classpath("aws.sdk.kotlin:build-plugins") {
1717
version {
18-
require("0.2.8")
18+
require("0.2.9")
1919
}
2020
}
2121
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/KotlinSettings.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,16 +263,20 @@ enum class DefaultValueSerializationMode(val value: String) {
263263
* @param visibility Enum representing the visibility of code-generated classes, objects, interfaces, etc.
264264
* @param nullabilityCheckMode Enum representing the nullability check mode to use
265265
* @param defaultValueSerializationMode Enum representing when default values should be serialized
266+
* @param enableEndpointAuthProvider flag indicating that endpoint resolution should be enabled as part of resolving
267+
* an auth scheme. This is an advanced option that only a select few service clients like S3 and EventBridge require.
266268
*/
267269
data class ApiSettings(
268270
val visibility: Visibility = Visibility.PUBLIC,
269271
val nullabilityCheckMode: CheckMode = CheckMode.CLIENT_CAREFUL,
270272
val defaultValueSerializationMode: DefaultValueSerializationMode = DefaultValueSerializationMode.WHEN_DIFFERENT,
273+
val enableEndpointAuthProvider: Boolean = false,
271274
) {
272275
companion object {
273276
const val VISIBILITY = "visibility"
274277
const val NULLABILITY_CHECK_MODE = "nullabilityCheckMode"
275278
const val DEFAULT_VALUE_SERIALIZATION_MODE = "defaultValueSerializationMode"
279+
const val ENABLE_ENDPOINT_AUTH_PROVIDER = "enableEndpointAuthProvider"
276280

277281
fun fromNode(node: Optional<ObjectNode>): ApiSettings = node.map {
278282
val visibility = node.get()
@@ -290,7 +294,8 @@ data class ApiSettings(
290294
DefaultValueSerializationMode.WHEN_DIFFERENT.value,
291295
),
292296
)
293-
ApiSettings(visibility, checkMode, defaultValueSerializationMode)
297+
val enableEndpointAuthProvider = node.get().getBooleanMemberOrDefault(ENABLE_ENDPOINT_AUTH_PROVIDER, false)
298+
ApiSettings(visibility, checkMode, defaultValueSerializationMode, enableEndpointAuthProvider)
294299
}.orElse(Default)
295300

296301
/**

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/AbstractCodeWriterExt.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.getConte
150150
*/
151151
inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.getContextValue(key: SectionKey<V>): V = getContextValue(key.name)
152152

153+
/**
154+
* Convenience function to set context only if there is no value already associated with the given [key]
155+
*/
156+
fun <W : AbstractCodeWriter<W>> AbstractCodeWriter<W>.putMissingContext(key: String, value: Any) {
157+
if (getContext(key) != null) return
158+
putContext(key, value)
159+
}
160+
153161
typealias InlineCodeWriter = AbstractCodeWriter<*>.() -> Unit
154162

155163
/**

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ object RuntimeTypes {
3535
object Request : RuntimeTypePackage(KotlinDependency.HTTP, "request") {
3636
val HttpRequest = symbol("HttpRequest")
3737
val HttpRequestBuilder = symbol("HttpRequestBuilder")
38+
val immutableView = symbol("immutableView")
3839
val url = symbol("url")
3940
val headers = symbol("headers")
4041
val toBuilder = symbol("toBuilder")
@@ -198,11 +199,8 @@ object RuntimeTypes {
198199
val EndpointProvider = symbol("EndpointProvider")
199200
val Endpoint = symbol("Endpoint")
200201
val EndpointProviderException = symbol("EndpointProviderException")
201-
val SigningContext = symbol("SigningContext")
202202
val SigningContextAttributeKey = symbol("SigningContextAttributeKey")
203-
204-
@get:JvmName("getSigningContextExtMethod")
205-
val signingContext = symbol("signingContext")
203+
val authOptions = symbol("authOptions")
206204
object Functions : RuntimeTypePackage(KotlinDependency.SMITHY_CLIENT, "endpoints.functions") {
207205
val substring = symbol("substring")
208206
val isValidHostLabel = symbol("isValidHostLabel")
@@ -314,7 +312,9 @@ object RuntimeTypes {
314312
object HttpAuthAws : RuntimeTypePackage(KotlinDependency.HTTP_AUTH_AWS) {
315313
val AwsHttpSigner = symbol("AwsHttpSigner")
316314
val SigV4AuthScheme = symbol("SigV4AuthScheme")
317-
val sigv4 = symbol("sigv4")
315+
val mergeAuthOptions = symbol("mergeAuthOptions")
316+
val sigV4 = symbol("sigV4")
317+
val sigV4A = symbol("sigV4A")
318318
}
319319
}
320320

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/ServiceClientConfigGenerator.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,13 @@ class ServiceClientConfigGenerator(
130130
.map { it.additionalServiceConfigProps(ctx).byName() }
131131
.forEach(allPropsByName::putAll)
132132

133+
writer.pushState()
134+
// Service client config is always nested inside the service client interface. Its visibility is taken
135+
// from that type. If the interface is `internal` then trying to use `internal` as the visibility on the
136+
// nested config class is a compilation error.
137+
writer.putContext("visibility", "public")
133138
super.render(ctx, allPropsByName.values.toList(), writer)
139+
writer.popState()
134140
}
135141

136142
override fun renderBuilderBuildMethod(writer: KotlinWriter) {

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeParametersGenerator.kt

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ import software.amazon.smithy.codegen.core.Symbol
99
import software.amazon.smithy.codegen.core.SymbolProvider
1010
import software.amazon.smithy.kotlin.codegen.KotlinSettings
1111
import software.amazon.smithy.kotlin.codegen.core.CodegenContext
12+
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
1213
import software.amazon.smithy.kotlin.codegen.core.clientName
1314
import software.amazon.smithy.kotlin.codegen.core.withBlock
1415
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
16+
import software.amazon.smithy.kotlin.codegen.model.asNullable
1517
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
18+
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointParametersGenerator
1619
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
1720
import software.amazon.smithy.kotlin.codegen.rendering.util.AbstractConfigGenerator
1821
import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigProperty
1922
import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigPropertyType
2023
import software.amazon.smithy.model.Model
2124

22-
// FIXME - TBD where parameters are actually sourced from.
23-
2425
/**
2526
* Generate the input type used for resolving authentication schemes
2627
*/
@@ -32,35 +33,13 @@ class AuthSchemeParametersGenerator : AbstractConfigGenerator() {
3233
namespace = "${settings.pkg.name}.auth"
3334
definitionFile = "$name.kt"
3435
}
35-
36-
fun getImplementationSymbol(settings: KotlinSettings): Symbol = buildSymbol {
37-
val prefix = clientName(settings.sdkId)
38-
name = "${prefix}AuthSchemeParametersImpl"
39-
namespace = "${settings.pkg.name}.auth"
40-
definitionFile = "$name.kt"
41-
}
4236
}
4337

44-
override val visibility: String = "internal"
45-
4638
fun render(ctx: ProtocolGenerator.GenerationContext) {
4739
val symbol = getSymbol(ctx.settings)
48-
val implSymbol = getImplementationSymbol(ctx.settings)
4940

5041
ctx.delegator.useSymbolWriter(symbol) { writer ->
51-
writer.withBlock(
52-
"#L interface #T {",
53-
"}",
54-
ctx.settings.api.visibility,
55-
symbol,
56-
) {
57-
dokka("The name of the operation currently being invoked.")
58-
write("public val operationName: String")
59-
}
60-
}
61-
62-
ctx.delegator.useSymbolWriter(implSymbol) { writer ->
63-
writer.putContext("configClass.name", implSymbol.name)
42+
writer.putContext("configClass.name", symbol.name)
6443

6544
val codegenCtx = object : CodegenContext {
6645
override val model: Model = ctx.model
@@ -76,11 +55,77 @@ class AuthSchemeParametersGenerator : AbstractConfigGenerator() {
7655
).toBuilder()
7756
.apply {
7857
propertyType = ConfigPropertyType.Required("operationName is a required auth scheme parameter")
79-
baseClass = symbol
8058
}.build()
8159

82-
val props = listOf(operationName)
60+
val endpointParamsProperty = ConfigProperty {
61+
name = "endpointParameters"
62+
this.symbol = EndpointParametersGenerator.getSymbol(ctx.settings).asNullable()
63+
documentation = """
64+
The parameters used for endpoint resolution. The default implementation of this interface
65+
relies on endpoint metadata to resolve auth scheme candidates.
66+
""".trimIndent()
67+
}.takeIf { ctx.settings.api.enableEndpointAuthProvider }
68+
69+
val props = listOfNotNull(operationName, endpointParamsProperty)
8370
render(codegenCtx, props, writer)
8471
}
8572
}
73+
74+
override fun renderAdditionalMethods(ctx: CodegenContext, props: List<ConfigProperty>, writer: KotlinWriter) {
75+
writer.write("")
76+
renderToString(ctx, props, writer)
77+
writer.write("")
78+
renderEquals(ctx, props, writer)
79+
writer.write("")
80+
renderHashCode(props, writer)
81+
writer.write("")
82+
renderCopy(ctx, props, writer)
83+
}
84+
85+
private fun renderEquals(ctx: CodegenContext, props: List<ConfigProperty>, writer: KotlinWriter) {
86+
writer.withBlock("override fun equals(other: Any?): Boolean {", "}") {
87+
write("if (this === other) return true")
88+
write("if (other !is #T) return false", getSymbol(ctx.settings))
89+
props.forEach { prop ->
90+
write("if (this.#1L != other.#1L) return false", prop.propertyName)
91+
}
92+
write("return true")
93+
}
94+
}
95+
private fun renderToString(ctx: CodegenContext, props: List<ConfigProperty>, writer: KotlinWriter) {
96+
writer.withBlock("override fun toString(): String = buildString {", "}") {
97+
write("append(\"#L(\")", getSymbol(ctx.settings).name)
98+
props.forEachIndexed { idx, prop ->
99+
write("""append("#1L=$#1L#2L")""", prop.propertyName, if (idx < props.size - 1) "," else ")")
100+
}
101+
}
102+
}
103+
104+
private fun renderHashCode(props: List<ConfigProperty>, writer: KotlinWriter) {
105+
writer.withBlock("override fun hashCode(): Int {", "}") {
106+
if (props.isEmpty()) {
107+
write("return this::class.hashCode()")
108+
return@withBlock
109+
}
110+
111+
write("var result = #L?.hashCode() ?: 0", props[0].propertyName)
112+
props.drop(1).forEach {
113+
write("result = 31 * result + (#L?.hashCode() ?: 0)", it.propertyName)
114+
}
115+
write("return result")
116+
}
117+
}
118+
119+
private fun renderCopy(ctx: CodegenContext, props: List<ConfigProperty>, writer: KotlinWriter) {
120+
val symbol = getSymbol(ctx.settings)
121+
writer.withBlock("#visibility:L fun copy(block: Builder.() -> Unit = {}): #T {", "}", symbol) {
122+
withBlock("return Builder().apply {", "}") {
123+
props.forEach {
124+
write("#1L = this@#2L.#1L", it.propertyName, symbol.name)
125+
}
126+
write("block()")
127+
}
128+
write(".build()")
129+
}
130+
}
86131
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeProviderAdapterGenerator.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
1111
import software.amazon.smithy.kotlin.codegen.core.clientName
1212
import software.amazon.smithy.kotlin.codegen.core.withBlock
1313
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
14+
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointResolverAdapterGenerator
1415
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
1516

1617
/**
@@ -32,10 +33,10 @@ class AuthSchemeProviderAdapterGenerator {
3233
ctx.delegator.useSymbolWriter(symbol) { writer ->
3334
writer.dokka("Adapts the service specific auth scheme resolver to the agnostic runtime interface and binds the auth parameters")
3435
writer.withBlock(
35-
"internal class #T(private val delegate: #T): #T {",
36+
"internal class #T(private val config: #T.Config): #T {",
3637
"}",
3738
symbol,
38-
AuthSchemeProviderGenerator.getSymbol(ctx.settings),
39+
ctx.symbolProvider.toSymbol(ctx.service),
3940
RuntimeTypes.HttpClient.Operation.AuthSchemeResolver,
4041
) {
4142
withBlock(
@@ -44,12 +45,23 @@ class AuthSchemeProviderAdapterGenerator {
4445
RuntimeTypes.HttpClient.Operation.SdkHttpRequest,
4546
RuntimeTypes.Auth.Identity.AuthOption,
4647
) {
47-
withBlock("val params = #T {", "}", AuthSchemeParametersGenerator.getImplementationSymbol(ctx.settings)) {
48+
withBlock("val params = #T {", "}", AuthSchemeParametersGenerator.getSymbol(ctx.settings)) {
4849
addImport(RuntimeTypes.Core.Utils.get)
4950
write("operationName = request.context[#T.OperationName]", RuntimeTypes.SmithyClient.SdkClientOption)
51+
52+
if (ctx.settings.api.enableEndpointAuthProvider) {
53+
write(
54+
"val resolveEndpointReq = #T(request.context, request.subject.#T(), #T)",
55+
RuntimeTypes.HttpClient.Operation.ResolveEndpointRequest,
56+
RuntimeTypes.Http.Request.immutableView,
57+
RuntimeTypes.Auth.HttpAuth.AnonymousIdentity,
58+
)
59+
60+
write("endpointParameters = #T(config, resolveEndpointReq)", EndpointResolverAdapterGenerator.getResolveEndpointParamsFn(ctx.settings))
61+
}
5062
}
5163

52-
write("return delegate.resolveAuthScheme(params)")
64+
write("return config.authSchemeProvider.resolveAuthScheme(params)")
5365
}
5466
}
5567
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeProviderConfigIntegration.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,20 @@ class AuthSchemeProviderConfigIntegration : KotlinIntegration {
1616
override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> {
1717
if (ctx.protocolGenerator == null) return super.additionalServiceConfigProps(ctx)
1818
val defaultProvider = AuthSchemeProviderGenerator.getDefaultSymbol(ctx.settings)
19+
1920
return listOf(
2021
ConfigProperty {
2122
name = "authSchemeProvider"
2223
symbol = AuthSchemeProviderGenerator.getSymbol(ctx.settings)
2324
documentation = "Configure the provider used to resolve the authentication scheme to use for a particular operation."
2425
additionalImports = listOf(defaultProvider)
25-
propertyType = ConfigPropertyType.RequiredWithDefault(defaultProvider.name)
26+
if (ctx.settings.api.enableEndpointAuthProvider) {
27+
propertyType = ConfigPropertyType.RequiredWithDefault("${defaultProvider.name}(endpointProvider)")
28+
} else {
29+
propertyType = ConfigPropertyType.RequiredWithDefault("${defaultProvider.name}()")
30+
}
31+
// needs to come after endpointProvider
32+
order = 100
2633
},
2734
)
2835
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeProviderGenerator.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import software.amazon.smithy.kotlin.codegen.integration.AuthSchemeHandler
1212
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
1313
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
1414
import software.amazon.smithy.kotlin.codegen.model.knowledge.AuthIndex
15+
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointProviderGenerator
1516
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
1617
import software.amazon.smithy.model.shapes.OperationShape
1718

@@ -63,12 +64,12 @@ open class AuthSchemeProviderGenerator {
6364
}
6465

6566
private fun renderDefaultImpl(ctx: ProtocolGenerator.GenerationContext, writer: KotlinWriter) {
66-
// FIXME - probably can't remain an object
6767
writer.withBlock(
68-
"#L object #T : #T {",
68+
"#L class #T(private val endpointProvider: #T? = null) : #T {",
6969
"}",
7070
ctx.settings.api.visibility,
7171
getDefaultSymbol(ctx.settings),
72+
EndpointProviderGenerator.getSymbol(ctx.settings),
7273
getSymbol(ctx.settings),
7374
) {
7475
val paramsSymbol = AuthSchemeParametersGenerator.getSymbol(ctx.settings)
@@ -108,9 +109,24 @@ open class AuthSchemeProviderGenerator {
108109
paramsSymbol,
109110
RuntimeTypes.Auth.Identity.AuthOption,
110111
) {
111-
withBlock("return operationOverrides.getOrElse(params.operationName) {", "}") {
112+
withBlock("val modeledAuthOptions = operationOverrides.getOrElse(params.operationName) {", "}") {
112113
write("serviceDefaults")
113114
}
115+
116+
if (ctx.settings.api.enableEndpointAuthProvider) {
117+
write("")
118+
write("val endpointParams = params.endpointParameters")
119+
openBlock("val endpointAuthOptions = if (endpointProvider != null && endpointParams != null) {")
120+
.write("val endpoint = endpointProvider.resolveEndpoint(endpointParams)")
121+
.write("endpoint.#T", RuntimeTypes.SmithyClient.Endpoints.authOptions)
122+
.closeAndOpenBlock("} else {")
123+
.write("emptyList()")
124+
.closeBlock("}")
125+
write("")
126+
write("return #T(modeledAuthOptions, endpointAuthOptions)", RuntimeTypes.Auth.HttpAuthAws.mergeAuthOptions)
127+
} else {
128+
write("return modeledAuthOptions")
129+
}
114130
}
115131

116132
// render any helper methods

0 commit comments

Comments
 (0)