Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyHeaderType
import aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyValue
import aws.sdk.kotlin.crt.auth.signing.AwsSigner
import aws.sdk.kotlin.crt.auth.signing.AwsSigningConfig
import aws.sdk.kotlin.crt.http.HttpRequest
import aws.sdk.kotlin.runtime.crt.path
import aws.sdk.kotlin.runtime.crt.queryParameters
import aws.sdk.kotlin.runtime.crt.toCrtHeaders
import aws.sdk.kotlin.runtime.crt.toSdkHeaders
import aws.sdk.kotlin.runtime.endpoint.EndpointResolver
import aws.smithy.kotlin.runtime.http.Headers
import aws.smithy.kotlin.runtime.http.HttpBody
import aws.smithy.kotlin.runtime.http.HttpMethod
import aws.smithy.kotlin.runtime.http.Protocol
import aws.smithy.kotlin.runtime.http.QueryParameters
import aws.smithy.kotlin.runtime.http.Url
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.util.InternalApi
import aws.sdk.kotlin.crt.http.HttpRequest as CrtHttpRequest

/**
* The service configuration details for a presigned request
Expand Down Expand Up @@ -63,26 +67,14 @@ public data class PresignedRequestConfig(
public val additionalHeaders: Headers = Headers.Empty
)

/**
* Properties of an HTTP request that has been presigned
* @property method HTTP method to use when initiating the request
* @property url HTTP url of the presigned request
* @property headers Headers that must be sent with the request
*/
public data class PresignedRequest(
val method: HttpMethod,
val url: String,
val headers: Headers
)

/**
* Generate a presigned request given the service and operation configurations.
* @param serviceConfig The service configuration to use in signing the request
* @param requestConfig The presign configuration to use in signing the request
* @return a [PresignedRequest] that can be executed by any HTTP client within the specified duration.
* @return a [HttpRequest] that can be executed by any HTTP client within the specified duration.
*/
@InternalApi
public suspend fun createPresignedRequest(serviceConfig: ServicePresignConfig, requestConfig: PresignedRequestConfig): PresignedRequest {
public suspend fun createPresignedRequest(serviceConfig: ServicePresignConfig, requestConfig: PresignedRequestConfig): HttpRequest {
val crtCredentials = serviceConfig.credentialsProvider.getCredentials().toCrt()
val endpoint = serviceConfig.endpointResolver.resolve(serviceConfig.serviceId, serviceConfig.region)

Expand All @@ -96,22 +88,28 @@ public suspend fun createPresignedRequest(serviceConfig: ServicePresignConfig, r
expirationInSeconds = requestConfig.durationSeconds
}

val url = Url(Protocol.HTTPS, endpoint.hostname, path = requestConfig.path, parameters = requestConfig.queryString)
val unsignedUrl = Url(Protocol.HTTPS, endpoint.hostname, path = requestConfig.path, parameters = requestConfig.queryString)

val headers = aws.sdk.kotlin.crt.http.Headers.build {
append("Host", endpoint.hostname)
appendAll(requestConfig.additionalHeaders.toCrtHeaders())
}
val request = HttpRequest(
val request = CrtHttpRequest(
requestConfig.method.name,
url.encodedPath,
headers
unsignedUrl.encodedPath,
aws.sdk.kotlin.crt.http.Headers.build {
append("Host", endpoint.hostname)
appendAll(requestConfig.additionalHeaders.toCrtHeaders())
}
)
val signedRequest = AwsSigner.signRequest(request, signingConfig)

return PresignedRequest(
requestConfig.method,
"${endpoint.protocol}://${endpoint.hostname}${signedRequest.encodedPath}",
signedRequest.headers.toSdkHeaders()
return HttpRequest(
method = HttpMethod.parse(signedRequest.method),
url = Url(
scheme = Protocol.HTTPS,
host = endpoint.hostname,
path = signedRequest.path(),
parameters = signedRequest.queryParameters() ?: QueryParameters.Empty,
encodeParameters = false
),
headers = signedRequest.headers.toSdkHeaders(),
body = HttpBody.Empty
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ object AwsRuntimeTypes {
val CredentialsProvider = runtimeSymbol("CredentialsProvider", AwsKotlinDependency.AWS_AUTH)
val createPresignedRequest = runtimeSymbol("createPresignedRequest", AwsKotlinDependency.AWS_AUTH)
val DefaultChainCredentialsProvider = runtimeSymbol("DefaultChainCredentialsProvider", AwsKotlinDependency.AWS_AUTH)
val PresignedRequest = runtimeSymbol("PresignedRequest", AwsKotlinDependency.AWS_AUTH)
val PresignedRequestConfig = runtimeSymbol("PresignedRequestConfig", AwsKotlinDependency.AWS_AUTH)
val ServicePresignConfig = runtimeSymbol("ServicePresignConfig", AwsKotlinDependency.AWS_AUTH)
val SigningLocation = runtimeSymbol("SigningLocation", AwsKotlinDependency.AWS_AUTH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ class PresignerGenerator : KotlinIntegration {
// Symbols which should be imported
private val presignerRuntimeSymbols = setOf(
// smithy-kotlin types
RuntimeTypes.Http.Request.HttpRequest,
RuntimeTypes.Core.ExecutionContext,
// AWS types
AwsRuntimeTypes.Auth.CredentialsProvider,
AwsRuntimeTypes.Auth.DefaultChainCredentialsProvider,
AwsRuntimeTypes.Auth.PresignedRequest,
AwsRuntimeTypes.Auth.PresignedRequestConfig,
AwsRuntimeTypes.Auth.ServicePresignConfig,
AwsRuntimeTypes.Auth.SigningLocation,
Expand Down Expand Up @@ -327,10 +327,10 @@ class PresignerGenerator : KotlinIntegration {
write("Presign a [$requestTypeName] using a [$serviceClientTypeName].")
write("@param serviceClient the client providing properties used to generate the presigned request.")
write("@param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.")
write("@return The [PresignedRequest] that can be invoked within the specified time window.")
write("@return The [HttpRequest] that can be invoked within the specified time window.")
}
// FIXME ~ Replace or add additional function, swap ULong type for kotlin.time.Duration when type becomes stable
writer.withBlock("suspend fun $requestTypeName.presign(serviceClient: $serviceClientTypeName, durationSeconds: ULong): PresignedRequest {", "}\n") {
writer.withBlock("suspend fun $requestTypeName.presign(serviceClient: $serviceClientTypeName, durationSeconds: ULong): HttpRequest {", "}\n") {
withBlock("val serviceClientConfig = $presignConfigTypeName {", "}") {
write("credentialsProvider = serviceClient.config.credentialsProvider ?: DefaultChainCredentialsProvider()")
write("endpointResolver = serviceClient.config.endpointResolver ?: DefaultEndpointResolver()")
Expand All @@ -345,10 +345,10 @@ class PresignerGenerator : KotlinIntegration {
write("Presign a [$requestTypeName] using a [ServicePresignConfig].")
write("@param serviceClientConfig the client configuration used to generate the presigned request")
write("@param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.")
write("@return The [PresignedRequest] that can be invoked within the specified time window.")
write("@return The [HttpRequest] that can be invoked within the specified time window.")
}
// FIXME ~ Replace or add additional function, swap ULong type for kotlin.time.Duration when type becomes stable
writer.withBlock("suspend fun $requestTypeName.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): PresignedRequest {", "}\n") {
writer.withBlock("suspend fun $requestTypeName.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): HttpRequest {", "}\n") {
write("return createPresignedRequest(serviceClientConfig, $requestConfigFnName(this, durationSeconds))")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ class PresignerGeneratorTest {
import aws.sdk.kotlin.runtime.ClientException
import aws.sdk.kotlin.runtime.auth.CredentialsProvider
import aws.sdk.kotlin.runtime.auth.DefaultChainCredentialsProvider
import aws.sdk.kotlin.runtime.auth.PresignedRequest
import aws.sdk.kotlin.runtime.auth.PresignedRequestConfig
import aws.sdk.kotlin.runtime.auth.ServicePresignConfig
import aws.sdk.kotlin.runtime.auth.SigningLocation
import aws.sdk.kotlin.runtime.auth.createPresignedRequest
import aws.sdk.kotlin.runtime.endpoint.EndpointResolver
import aws.smithy.kotlin.runtime.client.ExecutionContext
import aws.smithy.kotlin.runtime.http.QueryParameters
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import smithy.kotlin.traits.internal.DefaultEndpointResolver
import smithy.kotlin.traits.model.GetFooRequest
import smithy.kotlin.traits.model.PostFooRequest
Expand All @@ -109,19 +109,19 @@ class PresignerGeneratorTest {
* Presign a [GetFooRequest] using a [ServicePresignConfig].
* @param serviceClientConfig the client configuration used to generate the presigned request
* @param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.
* @return The [PresignedRequest] that can be invoked within the specified time window.
* @return The [HttpRequest] that can be invoked within the specified time window.
*/
suspend fun GetFooRequest.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): PresignedRequest {
suspend fun GetFooRequest.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): HttpRequest {
return createPresignedRequest(serviceClientConfig, getFooPresignConfig(this, durationSeconds))
}

/**
* Presign a [GetFooRequest] using a [TestClient].
* @param serviceClient the client providing properties used to generate the presigned request.
* @param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.
* @return The [PresignedRequest] that can be invoked within the specified time window.
* @return The [HttpRequest] that can be invoked within the specified time window.
*/
suspend fun GetFooRequest.presign(serviceClient: TestClient, durationSeconds: ULong): PresignedRequest {
suspend fun GetFooRequest.presign(serviceClient: TestClient, durationSeconds: ULong): HttpRequest {
val serviceClientConfig = TestPresignConfig {
credentialsProvider = serviceClient.config.credentialsProvider ?: DefaultChainCredentialsProvider()
endpointResolver = serviceClient.config.endpointResolver ?: DefaultEndpointResolver()
Expand All @@ -147,19 +147,19 @@ class PresignerGeneratorTest {
* Presign a [PostFooRequest] using a [ServicePresignConfig].
* @param serviceClientConfig the client configuration used to generate the presigned request
* @param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.
* @return The [PresignedRequest] that can be invoked within the specified time window.
* @return The [HttpRequest] that can be invoked within the specified time window.
*/
suspend fun PostFooRequest.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): PresignedRequest {
suspend fun PostFooRequest.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): HttpRequest {
return createPresignedRequest(serviceClientConfig, postFooPresignConfig(this, durationSeconds))
}

/**
* Presign a [PostFooRequest] using a [TestClient].
* @param serviceClient the client providing properties used to generate the presigned request.
* @param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.
* @return The [PresignedRequest] that can be invoked within the specified time window.
* @return The [HttpRequest] that can be invoked within the specified time window.
*/
suspend fun PostFooRequest.presign(serviceClient: TestClient, durationSeconds: ULong): PresignedRequest {
suspend fun PostFooRequest.presign(serviceClient: TestClient, durationSeconds: ULong): HttpRequest {
val serviceClientConfig = TestPresignConfig {
credentialsProvider = serviceClient.config.credentialsProvider ?: DefaultChainCredentialsProvider()
endpointResolver = serviceClient.config.endpointResolver ?: DefaultEndpointResolver()
Expand All @@ -185,19 +185,19 @@ class PresignerGeneratorTest {
* Presign a [PutFooRequest] using a [ServicePresignConfig].
* @param serviceClientConfig the client configuration used to generate the presigned request
* @param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.
* @return The [PresignedRequest] that can be invoked within the specified time window.
* @return The [HttpRequest] that can be invoked within the specified time window.
*/
suspend fun PutFooRequest.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): PresignedRequest {
suspend fun PutFooRequest.presign(serviceClientConfig: ServicePresignConfig, durationSeconds: ULong): HttpRequest {
return createPresignedRequest(serviceClientConfig, putFooPresignConfig(this, durationSeconds))
}

/**
* Presign a [PutFooRequest] using a [TestClient].
* @param serviceClient the client providing properties used to generate the presigned request.
* @param durationSeconds the amount of time from signing for which the request is valid, with seconds granularity.
* @return The [PresignedRequest] that can be invoked within the specified time window.
* @return The [HttpRequest] that can be invoked within the specified time window.
*/
suspend fun PutFooRequest.presign(serviceClient: TestClient, durationSeconds: ULong): PresignedRequest {
suspend fun PutFooRequest.presign(serviceClient: TestClient, durationSeconds: ULong): HttpRequest {
val serviceClientConfig = TestPresignConfig {
credentialsProvider = serviceClient.config.credentialsProvider ?: DefaultChainCredentialsProvider()
endpointResolver = serviceClient.config.endpointResolver ?: DefaultEndpointResolver()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import aws.sdk.kotlin.services.polly.model.OutputFormat
import aws.sdk.kotlin.services.polly.model.SynthesizeSpeechRequest
import aws.sdk.kotlin.services.polly.model.VoiceId
import aws.smithy.kotlin.runtime.http.HttpMethod
import aws.smithy.kotlin.runtime.http.Url
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
Expand Down Expand Up @@ -34,9 +33,8 @@ class PollyPresignerTest {
assertEquals(HttpMethod.GET, presignedRequest.method)
assertTrue(presignedRequest.headers.entries().size == 1)
assertEquals("polly.us-east-2.amazonaws.com", presignedRequest.headers["Host"])
val parsedUrl = Url.parse(presignedRequest.url)
assertEquals("/v1/speech", parsedUrl.path)
assertEquals("/v1/speech", presignedRequest.url.path)
val expectedQueryParameters = setOf("OutputFormat", "Text", "VoiceId", "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date", "X-Amz-SignedHeaders", "X-Amz-Expires", "X-Amz-Signature")
assertEquals(expectedQueryParameters, parsedUrl.parameters.entries().map { it.key }.toSet())
assertEquals(expectedQueryParameters, presignedRequest.url.parameters.entries().map { it.key }.toSet())
}
}