in app/helpers/S3Signer.scala [73:152]
protected def headersToMap(headers: Seq[HttpHeader]) = headers.map(head=>Tuple2(head.name, head.value)).toMap
/**
* Asynchronously signs the given [[HttpRequest]] object using the credentials provider given
* @param req Incoming HttpRequest
* @param region AWS region to access
* @param serviceName service name
* @param credsProvider AWSCredentialsProvider instance (or chain) that gives us credentials
* @return a Future with the updated [[HttpRequest]]
*/
def signHttpRequest(req:HttpRequest, region:Region, serviceName:String, credsProvider:AwsCredentialsProvider, timestamp:Option[OffsetDateTime]=None) = {
import scala.collection.immutable.Seq
val checksummer = MessageDigest.getInstance("SHA-256")
val requestTime = timestamp.getOrElse(OffsetDateTime.now(ZoneOffset.UTC))
val contentHashFuture = if(req.entity.isKnownEmpty()) {
logger.debug("request entity is empty")
Future(ByteString(checksummer.digest("".getBytes("UTF-8"))))
} else {
logger.debug("request entity has data")
req.entity.getDataBytes()
.via(new ContentHashingFlow("SHA-256"))
.runWith(Sink.reduce[ByteString](_.concat(_)), mat)
}
val contentHashHexFuture = contentHashFuture.map(bs=>bs.map("%02x".format(_)).mkString)
contentHashHexFuture.onComplete({
case Success(string)=>logger.debug(s"content hash string is $string")
case Failure(err)=>logger.error(s"failed to generate content hash", err)
})
val credentials = credsProvider.resolveCredentials()
val sessionTokenHeaders = credentials match {
case session:AwsSessionCredentials=>
Seq(makeHttpHeader("x-amz-security-token", session.sessionToken()))
case _=>
Seq()
}
val updatedHeadersFuture = contentHashHexFuture.map(hash=>
req.headers ++ Seq(
makeHttpHeader("x-amz-date",requestTime.format(aws_compatible_datetime)),
makeHttpHeader("x-amz-content-sha256",hash)
) ++ sessionTokenHeaders
)
val canonStringFuture = updatedHeadersFuture.map(headers=> {
val hash = contentHashHexFuture.value.get.get //this is safe, because updatedHeadersFuture is mapped from contentHashHexFuture; therefore if we got here, it succeeded.
calculateCanonicalString(req.method.value, req.uri.path.toString(), convertQueryString(req.uri.rawQueryString), headersToMap(headers), Some(hash))
})
canonStringFuture.onComplete({
case Success(str)=>logger.debug(s"canonicalString is $str")
case Failure(err)=>logger.error(s"Canonical string failed", err)
})
val stringToSignFuture = canonStringFuture.map(cs=>stringToSign(region.getName, serviceName, cs, requestTime))
stringToSignFuture.onComplete({
case Success(str)=>logger.debug(s"stringToSign is $str")
case Failure(err)=>logger.error(s"stringToSign failed", err)
})
val signingKeyResult = signingKey(credentials.secretAccessKey(), serviceName, region.getName, requestTime)
val sig = stringToSignFuture.map(sts=>finalSignature(signingKeyResult, sts))
Future.sequence(Seq(sig, updatedHeadersFuture)).map(results=>{
val finalSig = results.head.asInstanceOf[String]
val signedHeaders = results(1).asInstanceOf[Seq[HttpHeader]]
val signedHeadersString = signedHeaders.map(_.name().toLowerCase()).sorted.mkString(";")
val finalHeaders = signedHeaders ++ Seq(
makeHttpHeader("Authorization", s"AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId()}/${requestTime.format(aws_compatible_date)}/$region/$serviceName/aws4_request,SignedHeaders=$signedHeadersString,Signature=$finalSig")
)
logger.debug(s"Final headers are: $finalHeaders")
req.withHeaders(finalHeaders)
})
}