override suspend fun modifyRequest()

in aws-runtime/aws-signing/common/src/aws/sdk/kotlin/runtime/auth/signing/AwsSigV4SigningMiddleware.kt [92:154]


    override suspend fun modifyRequest(req: SdkHttpRequest): SdkHttpRequest {
        val credentialsProvider = checkNotNull(config.credentialsProvider)
        val resolvedCredentials = credentialsProvider.getCredentials()
        val logger = req.context.getLogger("AwsSigv4SigningMiddleware")

        val isUnsignedRequest = req.context.isUnsignedRequest()
        // FIXME - an alternative here would be to just pre-compute the sha256 of the payload ourselves and set
        //         the signed body value on the signing config. This would prevent needing to launch a coroutine
        //         for streaming requests since we already have a suspend context.
        val signableRequest = req.subject.toSignableCrtRequest(isUnsignedRequest)

        // SDKs are supposed to default to signed payload _always_ when possible (and when `unsignedPayload` trait isn't present).
        //
        // There are a few escape hatches/special cases:
        //     1. Customer explicitly disables signed payload (via AuthAttributes.UnsignedPayload)
        //     2. Customer provides a (potentially) unbounded stream (via HttpBody.Streaming)
        //
        // When an unbounded stream (2) is given we proceed as follows:
        //     2.1. is it replayable?
        //          (2.1.1) yes -> sign the payload (stream can be consumed more than once)
        //          (2.1.2) no -> unsigned payload
        //
        // NOTE: Chunked signing is NOT enabled through this middleware.
        // NOTE: 2.1.2 is handled below

        // FIXME - see: https://github.com/awslabs/smithy-kotlin/issues/296
        // if we know we have a (streaming) body and toSignableRequest() fails to convert it to a CRT equivalent
        // then we must decide how to compute the payload hash ourselves (defaults to unsigned payload)
        val isUnboundedStream = signableRequest.body == null && req.subject.body is HttpBody.Streaming

        // operation signing config is baseConfig + operation specific config/overrides
        val opSigningConfig = AwsSigningConfig {
            region = req.context[AuthAttributes.SigningRegion]
            service = req.context.getOrNull(AuthAttributes.SigningService) ?: checkNotNull(config.signingService)
            credentials = resolvedCredentials
            algorithm = config.algorithm
            date = req.context.getOrNull(AuthAttributes.SigningDate)

            signatureType = config.signatureType
            omitSessionToken = config.omitSessionToken
            normalizeUriPath = config.normalizeUriPath
            useDoubleUriEncode = config.useDoubleUriEncode
            expiresAfter = config.expiresAfter

            signedBodyHeader = config.signedBodyHeaderType
            signedBodyValue = when {
                isUnsignedRequest -> AwsSignedBodyValue.UNSIGNED_PAYLOAD
                req.subject.body is HttpBody.Empty -> AwsSignedBodyValue.EMPTY_SHA256
                isUnboundedStream -> {
                    logger.warn { "unable to compute hash for unbounded stream; defaulting to unsigned payload" }
                    AwsSignedBodyValue.UNSIGNED_PAYLOAD
                }
                // use the payload to compute the hash
                else -> null
            }
        }

        val signedRequest = AwsSigner.signRequest(signableRequest, opSigningConfig.toCrt())
        req.subject.update(signedRequest)
        req.subject.body.resetStream()

        return req
    }