Skip to content

Commit

Permalink
Support signing sttp requests
Browse files Browse the repository at this point in the history
- Add support for signing com.softwaremill.sttp requests
- Consolidate logic for extracting the path & query parameter from the request type of each library using java.net.URI (since every library normally provides a well-tested conversion to java.net.URI). akka-http and sttp signer now uses it
- Disable mauth-test-utils publishing
- Rename FixturesLoader to TestFixtures. Consolidate test constants from mauth-signer-akka-http's tests
  • Loading branch information
jatcwang committed Jun 9, 2020
1 parent 4f3a07e commit 0d21bbd
Show file tree
Hide file tree
Showing 20 changed files with 568 additions and 196 deletions.
9 changes: 7 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ before_install:
jobs:
include:
- stage: test
script: sbt scalafmtSbtCheck scalafmtCheckAll +test:compile +suggestNextVersionAcrossAllProjects +test
name: Test
script: sbt scalafmtSbtCheck scalafmtCheckAll

- stage: test
name: Format check
script: sbt +test:compile +suggestNextVersionAcrossAllProjects +test

- stage: release
script: .travis/deploy.sh
if: tag IS present

stages:
- test
- Test
- release
17 changes: 16 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ lazy val `mauth-common` = (project in file("modules/mauth-common"))
lazy val `mauth-test-utils` = (project in file("modules/mauth-test-utils"))
.settings(
basicSettings,
publishSettings,
noPublishSettings,
javaProjectSettings,
name := "mauth-test-utils",
libraryDependencies ++=
Expand Down Expand Up @@ -91,6 +91,20 @@ lazy val `mauth-signer-akka-http` = (project in file("modules/mauth-signer-akka-
Dependencies.test(scalaMock, scalaTest, wiremock).map(withExclusions)
)

lazy val `mauth-signer-sttp` = (project in file("modules/mauth-signer-sttp"))
.dependsOn(`mauth-signer`, `mauth-test-utils` % "test")
.settings(
basicSettings,
publishSettings,
scalaProjectSettings,
name := "mauth-signer-sttp",
libraryDependencies ++=
Dependencies.compile(catsEffect, akkaHttp, akkaStream, scalaLibCompat, sttp, scalaLogging).map(withExclusions) ++
Dependencies.test(scalaMock, scalaTest, wiremock, sttpAkkaHttpBackend).map(withExclusions),
// TODO remove once published
mimaPreviousArtifacts := Set.empty
)

lazy val `mauth-authenticator` = (project in file("modules/mauth-authenticator"))
.dependsOn(`mauth-common`)
.settings(
Expand Down Expand Up @@ -190,6 +204,7 @@ lazy val `mauth-jvm-clients` = (project in file("."))
`mauth-proxy`,
`mauth-signer`,
`mauth-signer-akka-http`,
`mauth-signer-sttp`,
`mauth-signer-apachehttp`,
`mauth-test-utils`
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import com.mdsol.mauth.MAuthRequest
import com.mdsol.mauth.http.{`X-MWS-Authentication`, `X-MWS-Time`}
import com.mdsol.mauth.scaladsl.RequestAuthenticator
import com.mdsol.mauth.scaladsl.utils.ClientPublicKeyProvider
import com.mdsol.mauth.test.utils.FixturesLoader
import com.mdsol.mauth.test.utils.TestFixtures
import com.mdsol.mauth.util.{EpochTimeProvider, MAuthKeysHelper}
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.scalamock.scalatest.MockFactory
Expand Down Expand Up @@ -51,7 +51,7 @@ class MAuthDirectivesSpec extends AnyWordSpec with Matchers with ScalatestRouteT

"authenticate" should {
lazy val route: Route = authenticate(authenticator, timeout, requestValidationTimeout).apply(complete(HttpResponse()))
val publicKey = MAuthKeysHelper.getPublicKeyFromString(FixturesLoader.getPublicKey)
val publicKey = MAuthKeysHelper.getPublicKeyFromString(TestFixtures.PUBLIC_KEY_1)

"pass successfully authenticated request" in {
(client.getPublicKey _).expects(appUuid).returns(Future(Some(publicKey)))
Expand Down Expand Up @@ -228,7 +228,7 @@ class MAuthDirectivesSpec extends AnyWordSpec with Matchers with ScalatestRouteT

"authenticate when v2OnlyAuthenticate = true" should {
lazy val route: Route = authenticate(authenticatorV2, timeout, requestValidationTimeout).apply(complete(HttpResponse()))
val publicKey = MAuthKeysHelper.getPublicKeyFromString(FixturesLoader.getPublicKey)
val publicKey = MAuthKeysHelper.getPublicKeyFromString(TestFixtures.PUBLIC_KEY_1)

"pass successfully authenticated request with both v1 and v2 headers, with V2 headers taking precedence" in {
(client.getPublicKey _).expects(appUuid).returns(Future(Some(publicKey)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import java.nio.charset.StandardCharsets
import java.security.Security

import com.mdsol.mauth.test.utils.FakeMAuthServer.EXISTING_CLIENT_APP_UUID
import com.mdsol.mauth.test.utils.FixturesLoader
import com.mdsol.mauth.test.utils.TestFixtures
import com.mdsol.mauth.util.EpochTimeProvider
import org.apache.http.client.methods.{HttpGet, HttpPost}
import org.bouncycastle.jce.provider.BouncyCastleProvider
Expand All @@ -17,7 +17,7 @@ trait RequestAuthenticatorBaseSpec extends AnyFlatSpec with BeforeAndAfterAll wi
val CLIENT_X_MWS_TIME_HEADER_VALUE = "1444672122"
val CLIENT_UNICODE_X_MWS_TIME_HEADER_VALUE = "1444748974"
val CLIENT_NO_BODY_X_MWS_TIME_HEADER_VALUE = "1424700000"
val PUBLIC_KEY: String = FixturesLoader.getPublicKey
val PUBLIC_KEY: String = TestFixtures.PUBLIC_KEY_1
val REQUEST_VALIDATION_TIMEOUT_SECONDS = 300L
val CLIENT_REQUEST_SIGNATURE: String =
"""fFQzIOo4S1MxxmEDB9v7v0IYNytnS3I5aHNeJfEfFe1v1gTE/cH36BfLG/zp
Expand Down Expand Up @@ -138,30 +138,30 @@ trait RequestAuthenticatorBaseSpec extends AnyFlatSpec with BeforeAndAfterAll wi
.build
}

val CLIENT_REQUEST_BINARY_APP_UUID = FixturesLoader.APP_UUID_V2
val CLIENT_REQUEST_BINARY_TIME_HEADER_VALUE = FixturesLoader.EPOCH_TIME_V2
val PUBLIC_KEY2: String = FixturesLoader.getPublicKey2
val CLIENT_REQUEST_AUTHENTICATION_BINARY_HEADER_V1: String = "MWS " + FixturesLoader.APP_UUID_V2 + ":" + FixturesLoader.SIGNATURE_V1_BINARY
val CLIENT_REQUEST_BINARY_APP_UUID = TestFixtures.APP_UUID_V2
val CLIENT_REQUEST_BINARY_TIME_HEADER_VALUE = TestFixtures.EPOCH_TIME
val PUBLIC_KEY2: String = TestFixtures.PUBLIC_KEY_2
val CLIENT_REQUEST_AUTHENTICATION_BINARY_HEADER_V1: String = "MWS " + TestFixtures.APP_UUID_V2 + ":" + TestFixtures.SIGNATURE_V1_BINARY
def getRequestWithBinaryBodyV1: MAuthRequest = {
MAuthRequest.Builder.get
.withAuthenticationHeaderValue(CLIENT_REQUEST_AUTHENTICATION_BINARY_HEADER_V1)
.withTimeHeaderValue(CLIENT_REQUEST_BINARY_TIME_HEADER_VALUE)
.withHttpMethod(FixturesLoader.REQUEST_METHOD_V2)
.withMessagePayload(FixturesLoader.getBinaryFileBody)
.withResourcePath(FixturesLoader.REQUEST_PATH_V2)
.withQueryParameters(FixturesLoader.REQUEST_QUERY_PARAMETERS_V2)
.withHttpMethod(TestFixtures.REQUEST_METHOD_V2)
.withMessagePayload(TestFixtures.BINARY_FILE_BODY)
.withResourcePath(TestFixtures.REQUEST_PATH_V2)
.withQueryParameters(TestFixtures.REQUEST_QUERY_PARAMETERS_V2)
.build
}

val CLIENT_REQUEST_AUTHENTICATION_BINARY_HEADER_V2: String = "MWSV2 " + CLIENT_REQUEST_BINARY_APP_UUID + ":" + FixturesLoader.SIGNATURE_V2_BINARY
val CLIENT_REQUEST_AUTHENTICATION_BINARY_HEADER_V2: String = "MWSV2 " + CLIENT_REQUEST_BINARY_APP_UUID + ":" + TestFixtures.SIGNATURE_V2_BINARY
def getRequestWithBinaryBodyV2: MAuthRequest = {
MAuthRequest.Builder.get
.withAuthenticationHeaderValue(CLIENT_REQUEST_AUTHENTICATION_BINARY_HEADER_V2)
.withTimeHeaderValue(CLIENT_REQUEST_BINARY_TIME_HEADER_VALUE)
.withHttpMethod(FixturesLoader.REQUEST_METHOD_V2)
.withMessagePayload(FixturesLoader.getBinaryFileBody)
.withResourcePath(FixturesLoader.REQUEST_PATH_V2)
.withQueryParameters(FixturesLoader.REQUEST_QUERY_PARAMETERS_V2)
.withHttpMethod(TestFixtures.REQUEST_METHOD_V2)
.withMessagePayload(TestFixtures.BINARY_FILE_BODY)
.withResourcePath(TestFixtures.REQUEST_PATH_V2)
.withQueryParameters(TestFixtures.REQUEST_QUERY_PARAMETERS_V2)
.build
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import java.nio.charset.StandardCharsets
import java.security.Security
import java.util.UUID

import com.mdsol.mauth.test.utils.FixturesLoader
import com.mdsol.mauth.test.utils.TestFixtures
import com.mdsol.mauth.util.MAuthKeysHelper.{getPrivateKeyFromString, getPublicKeyFromString}
import com.mdsol.mauth.util.MAuthSignatureHelper
import org.bouncycastle.jce.provider.BouncyCastleProvider
Expand All @@ -21,14 +21,14 @@ class MAuthSignatureHelperSpec extends AnyFlatSpec with Matchers {
private val CLIENT_REQUEST_PAYLOAD = "message here"
private val CLIENT_REQUEST_QUERY_PARAMETERS = "key1=value1&key2=value2"
private val TEST_EPOCH_TIME = 1424700000L
private val TEST_PRIVATE_KEY = getPrivateKeyFromString(FixturesLoader.getPrivateKey2)
private val TEST_PRIVATE_KEY = getPrivateKeyFromString(TestFixtures.PRIVATE_KEY_2)

// the same test data with ruby and python
private val CLIENT_APP_UUID_V2 = FixturesLoader.APP_UUID_V2
private val CLIENT_REQUEST_METHOD_V2 = FixturesLoader.REQUEST_METHOD_V2
private val CLIENT_REQUEST_PATH_V2 = FixturesLoader.REQUEST_PATH_V2
private val CLIENT_REQUEST_QUERY_PARAMETERS_V2 = FixturesLoader.REQUEST_QUERY_PARAMETERS_V2
private val TEST_EPOCH_TIME_V2 = FixturesLoader.EPOCH_TIME_V2
private val CLIENT_APP_UUID_V2 = TestFixtures.APP_UUID_V2
private val CLIENT_REQUEST_METHOD_V2 = TestFixtures.REQUEST_METHOD_V2
private val CLIENT_REQUEST_PATH_V2 = TestFixtures.REQUEST_PATH_V2
private val CLIENT_REQUEST_QUERY_PARAMETERS_V2 = TestFixtures.REQUEST_QUERY_PARAMETERS_V2
private val TEST_EPOCH_TIME_V2 = TestFixtures.EPOCH_TIME

behavior of "MAuthSignatureHelper"

Expand Down Expand Up @@ -136,8 +136,8 @@ class MAuthSignatureHelperSpec extends AnyFlatSpec with Matchers {

it should "verify signature for V2" in {
val testString = "Hello world"
val signString = MAuthSignatureHelper.encryptSignatureRSA(getPrivateKeyFromString(FixturesLoader.getPrivateKey), testString)
MAuthSignatureHelper.verifyRSA(testString, signString, getPublicKeyFromString(FixturesLoader.getPublicKey)) shouldBe true
val signString = MAuthSignatureHelper.encryptSignatureRSA(getPrivateKeyFromString(TestFixtures.PRIVATE_KEY_1), testString)
MAuthSignatureHelper.verifyRSA(testString, signString, getPublicKeyFromString(TestFixtures.PUBLIC_KEY_1)) shouldBe true
}

it should "correctly generate signature of binary body for V2" in {
Expand All @@ -146,10 +146,10 @@ class MAuthSignatureHelperSpec extends AnyFlatSpec with Matchers {
CLIENT_REQUEST_METHOD_V2,
CLIENT_REQUEST_PATH_V2,
CLIENT_REQUEST_QUERY_PARAMETERS_V2,
FixturesLoader.getBinaryFileBody,
TestFixtures.BINARY_FILE_BODY,
TEST_EPOCH_TIME_V2
)
MAuthSignatureHelper.encryptSignatureRSA(TEST_PRIVATE_KEY, testString) shouldBe FixturesLoader.SIGNATURE_V2_BINARY
MAuthSignatureHelper.encryptSignatureRSA(TEST_PRIVATE_KEY, testString) shouldBe TestFixtures.SIGNATURE_V2_BINARY
}

it should "correctly generate signature with empty body for V2" in {
Expand All @@ -161,7 +161,7 @@ class MAuthSignatureHelperSpec extends AnyFlatSpec with Matchers {
Array.empty,
TEST_EPOCH_TIME_V2
)
MAuthSignatureHelper.encryptSignatureRSA(TEST_PRIVATE_KEY, testString) shouldBe FixturesLoader.SIGNATURE_V2_EMPTY
MAuthSignatureHelper.encryptSignatureRSA(TEST_PRIVATE_KEY, testString) shouldBe TestFixtures.SIGNATURE_V2_EMPTY
}

it should "normalize path: correctly generate signature for V2" in {
Expand All @@ -172,10 +172,10 @@ class MAuthSignatureHelperSpec extends AnyFlatSpec with Matchers {
CLIENT_REQUEST_METHOD_V2,
resourcePath,
CLIENT_REQUEST_QUERY_PARAMETERS_V2,
FixturesLoader.getBinaryFileBody,
TestFixtures.BINARY_FILE_BODY,
TEST_EPOCH_TIME_V2
)
MAuthSignatureHelper.encryptSignatureRSA(TEST_PRIVATE_KEY, testString) shouldBe FixturesLoader.SIGNATURE_V2_BINARY
MAuthSignatureHelper.encryptSignatureRSA(TEST_PRIVATE_KEY, testString) shouldBe TestFixtures.SIGNATURE_V2_BINARY
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ class MAuthRequestSigner(appUUID: UUID, privateKey: PrivateKey, epochTimeProvide
}

override def signRequest(request: NewUnsignedRequest): NewSignedRequest = {
val headers = generateRequestHeaders(request.httpMethod, request.uri.getRawPath, request.body, request.uri.getRawQuery).asScala.toMap
val javaUri = request.uri
val headers = SignerUtils.signWithUri(this, request.httpMethod, javaUri, request.body).asScala.toMap
NewSignedRequest(
request,
headers
Expand Down
Loading

0 comments on commit 0d21bbd

Please sign in to comment.