From 74f3cd10efa0abdb6341826e845b0663e05143ff Mon Sep 17 00:00:00 2001 From: bgong-mdsol Date: Thu, 15 Apr 2021 11:54:38 -0400 Subject: [PATCH] [MCC-750736]support mauth-protocol-test-suite (#133) --- .gitmodules | 3 + CHANGELOG.md | 2 + CONTRIBUTING.adoc | 11 ++ build.sbt | 4 +- mauth-protocol-test-suite | 1 + .../mdsol/mauth/MauthProtocolSuiteSpec.scala | 115 +++++++++++++ .../test/utils/ProtocolTestSuiteHelper.java | 159 ++++++++++++++++++ .../utils/model/AuthenticationHeader.java | 18 ++ .../test/utils/model/AuthenticationOnly.java | 30 ++++ .../mauth/test/utils/model/CaseType.java | 6 + .../utils/model/SigningAuthentication.java | 44 +++++ .../mauth/test/utils/model/SigningConfig.java | 33 ++++ .../mauth/test/utils/model/TestCase.java | 6 + .../test/utils/model/UnsignedRequest.java | 57 +++++++ 14 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 160000 mauth-protocol-test-suite create mode 100644 modules/mauth-authenticator-scala/src/test/scala/com/mdsol/mauth/MauthProtocolSuiteSpec.scala create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/ProtocolTestSuiteHelper.java create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationHeader.java create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationOnly.java create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/CaseType.java create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningAuthentication.java create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningConfig.java create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/TestCase.java create mode 100644 modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/UnsignedRequest.java diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..a09c9a9a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mauth-protocol-test-suite"] + path = mauth-protocol-test-suite + url = https://github.com/mdsol/mauth-protocol-test-suite.git diff --git a/CHANGELOG.md b/CHANGELOG.md index c0cf31d5..f121c05b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- parsing code to test with mauth-protocol-test-suite. ### Changed - Dependency update diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index 6ae4070c..c59247c1 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -8,6 +8,17 @@ $ cd mauth-jvm-clients $ git checkout develop ---- +This repo contains the submodule `mauth-protocol-test-suite` so requires a flag when initially cloning in order to clone and init submodules. +---- +$ git clone --recurse-submodules git@github.com:mdsol/mauth-jvm-clients.git +---- + +If you have already cloned before the submodule was introduced, then run: +---- +$ cd mauth-protocol-test-suite +$ git submodule update --init +---- + == Git Hooks Install desired hook(s) diff --git a/build.sbt b/build.sbt index f5919810..f2b64517 100644 --- a/build.sbt +++ b/build.sbt @@ -118,11 +118,11 @@ lazy val `mauth-authenticator` = javaModuleProject("mauth-authenticator") ) lazy val `mauth-authenticator-scala` = scalaModuleProject("mauth-authenticator-scala") - .dependsOn(`mauth-authenticator`, `mauth-test-utils` % "test") + .dependsOn(`mauth-authenticator`, `mauth-signer` % "test", `mauth-test-utils` % "test") .settings( publishSettings, libraryDependencies ++= - Dependencies.test(logbackClassic, scalaMock, scalaTest).map(withExclusions) + Dependencies.test(logbackClassic, scalaMock, scalaTest, scalaLibCompat).map(withExclusions) ) lazy val `mauth-authenticator-apachehttp` = javaModuleProject("mauth-authenticator-apachehttp") diff --git a/mauth-protocol-test-suite b/mauth-protocol-test-suite new file mode 160000 index 00000000..303bf5e8 --- /dev/null +++ b/mauth-protocol-test-suite @@ -0,0 +1 @@ +Subproject commit 303bf5e877d1fd5099dc0ed7fef2c1f8582f36e3 diff --git a/modules/mauth-authenticator-scala/src/test/scala/com/mdsol/mauth/MauthProtocolSuiteSpec.scala b/modules/mauth-authenticator-scala/src/test/scala/com/mdsol/mauth/MauthProtocolSuiteSpec.scala new file mode 100644 index 00000000..23c875f5 --- /dev/null +++ b/modules/mauth-authenticator-scala/src/test/scala/com/mdsol/mauth/MauthProtocolSuiteSpec.scala @@ -0,0 +1,115 @@ +package com.mdsol.mauth + +import java.util.UUID + +import com.mdsol.mauth.test.utils.model._ +import com.mdsol.mauth.test.utils.ProtocolTestSuiteHelper +import com.mdsol.mauth.util.MAuthKeysHelper.getPrivateKeyFromString +import com.mdsol.mauth.util.{EpochTimeProvider, MAuthKeysHelper, MAuthSignatureHelper} +import com.mdsol.mauth.utils.ClientPublicKeyProvider +import org.scalamock.scalatest.MockFactory +import org.scalatest.BeforeAndAfterAll +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import scala.jdk.CollectionConverters._ + +class MauthProtocolSuiteSpec extends AnyFlatSpec with BeforeAndAfterAll with Matchers with MockFactory { + + val REQUEST_VALIDATION_TIMEOUT_SECONDS: Long = 300L + val mockClientPublicKeyProvider: ClientPublicKeyProvider = mock[ClientPublicKeyProvider] + val mockEpochTimeProvider: EpochTimeProvider = mock[EpochTimeProvider] + val authenticatorV2: RequestAuthenticator = + new RequestAuthenticator(mockClientPublicKeyProvider, REQUEST_VALIDATION_TIMEOUT_SECONDS, mockEpochTimeProvider, true) + + behavior of "MauthProtocolSuiteSpec" + + val signingConfig = ProtocolTestSuiteHelper.SIGNING_CONFIG + if (signingConfig == null) { + fail("Signing Configuration is not available.") + } + + if (ProtocolTestSuiteHelper.PUBLIC_KEY == null) { + fail("Public Key is not available.") + } + + val publicKey = MAuthKeysHelper.getPublicKeyFromString(ProtocolTestSuiteHelper.PUBLIC_KEY) + val uuid = UUID.fromString(signingConfig.getAppUuid) + val privateKey = getPrivateKeyFromString(signingConfig.getPrivateKey) + val mAuthSigner = new DefaultSigner(uuid, privateKey, mockEpochTimeProvider, java.util.Arrays.asList(MAuthVersion.MWSV2)) + + // run the tests + val testSpecifications = ProtocolTestSuiteHelper.TEST_SPECIFICATIONS + testSpecifications.foreach { testSpec => + testSpec.getType match { + case CaseType.AUTHENTICATION_ONLY => + val authenticationOnly = testSpec.asInstanceOf[AuthenticationOnly] + s"Test Case: ${authenticationOnly.getName}" should "pass authentication" in { + doAuthentication( + authenticationOnly.getUnsignedRequest, + authenticationOnly.getAuthenticationHeader + ) shouldBe true + } + + case CaseType.SIGNING_AUTHENTICATION => + val signingAuthentication = testSpec.asInstanceOf[SigningAuthentication] + val authHeader = signingAuthentication.getAuthenticationHeader + val unsignedRequest = signingAuthentication.getUnsignedRequest + + val httpVerb = unsignedRequest.getHttpVerb + val resourcePath = unsignedRequest.getResourcePath + val queryString = unsignedRequest.getQueryString + val bodyInBytes = unsignedRequest.getBodyInBytes + s"Test Case: ${signingAuthentication.getName}" should "correctly generate the string to sign" in { + MAuthSignatureHelper.generateStringToSignV2( + uuid, + httpVerb, + resourcePath, + queryString, + bodyInBytes, + signingConfig.getRequestTime + ) shouldBe signingAuthentication.getStringToSign + } + + it should "correctly generate the signature" in { + MAuthSignatureHelper.encryptSignatureRSA( + privateKey, + signingAuthentication.getStringToSign + ) shouldBe signingAuthentication.getSignature + } + + it should "correctly generate the authentication headers" in { + (mockEpochTimeProvider.inSeconds _: () => Long).expects().returns(signingConfig.getRequestTime.toLong) + val headers: Map[String, String] = + mAuthSigner + .generateRequestHeaders(httpVerb, resourcePath, bodyInBytes, queryString) + .asScala + .toMap + headers(MAuthRequest.MCC_AUTHENTICATION_HEADER_NAME) shouldBe authHeader.getMccAuthentication + headers(MAuthRequest.MCC_TIME_HEADER_NAME) shouldBe authHeader.getMccTime.toString + } + + it should "pass authentication" in { + doAuthentication(unsignedRequest, authHeader) shouldBe true + } + } + } + + private def doAuthentication(unsignedRequest: UnsignedRequest, authHeader: AuthenticationHeader): Boolean = { + val mauthRequest = MAuthRequest.Builder.get + .withAuthenticationHeaderValue(authHeader.getMccAuthentication) + .withTimeHeaderValue(String.valueOf(authHeader.getMccTime)) + .withHttpMethod(unsignedRequest.getHttpVerb) + .withResourcePath(unsignedRequest.getResourcePath) + .withQueryParameters(unsignedRequest.getQueryString) + .withMessagePayload(unsignedRequest.getBodyInBytes) + .build() + + (mockEpochTimeProvider.inSeconds _: () => Long).expects().returns(signingConfig.getRequestTime.toLong + 3) + (mockClientPublicKeyProvider.getPublicKey _) + .expects(uuid) + .returns(publicKey) + authenticatorV2.authenticate(mauthRequest) + } + +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/ProtocolTestSuiteHelper.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/ProtocolTestSuiteHelper.java new file mode 100644 index 00000000..9ed9345c --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/ProtocolTestSuiteHelper.java @@ -0,0 +1,159 @@ +package com.mdsol.mauth.test.utils; + +import com.mdsol.mauth.test.utils.model.AuthenticationHeader; +import com.mdsol.mauth.test.utils.model.AuthenticationOnly; +import com.mdsol.mauth.test.utils.model.SigningAuthentication; +import com.mdsol.mauth.test.utils.model.SigningConfig; +import com.mdsol.mauth.test.utils.model.TestCase; +import com.mdsol.mauth.test.utils.model.UnsignedRequest; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class ProtocolTestSuiteHelper { + + final static String TEST_SUITE_RELATIVE_PATH = "../../mauth-protocol-test-suite/"; + final static String MWSV2_TEST_CASE_PATH = getFullFilePath("protocols/MWSV2/"); + + public final static SigningConfig SIGNING_CONFIG; + static { + SigningConfig tmp = null; + String configFile = getFullFilePath("signing-config.json"); + try { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] jsonData = Files.readAllBytes(normalizePath(configFile)); + tmp = objectMapper.readValue(jsonData, SigningConfig.class); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load the config file " + configFile, ex); + } + SIGNING_CONFIG = tmp; + } + + public final static String PUBLIC_KEY ; + static { + String tmp = null; + String filePath = getFullFilePath("signing-params/rsa-key-pub"); + try { + tmp = new String(Files.readAllBytes(normalizePath(filePath)), StandardCharsets.UTF_8); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load the public key " + filePath, ex); + } + PUBLIC_KEY = tmp; + } + + public final static TestCase[] TEST_SPECIFICATIONS; + static { + List testCaseList = new ArrayList(); + String[] cases = retrieveV2TestCases(); + + for (String caseName : cases) { + String caseFile = caseName.concat("/").concat(caseName); + AuthenticationHeader authHeader = loadAuthenticationHeader(caseFile.concat(".authz")); + UnsignedRequest unsignedRequest = loadUnsignedRequest(caseFile.concat(".req")); + byte[] bodyInBytes = getTestRequestBody(unsignedRequest, caseName); + unsignedRequest.setBodyInBytes(bodyInBytes); + + boolean isAuthenticationOnly = caseName.contains("authentication-only"); + if(isAuthenticationOnly) { + testCaseList.add(new AuthenticationOnly(caseName, unsignedRequest, authHeader)); + } else { + String stringToSign = loadTestDataAsString(caseFile.concat(".sts")); + String signature = loadTestDataAsString(caseFile.concat(".sig")); + testCaseList.add(new SigningAuthentication(caseName, unsignedRequest, authHeader, stringToSign, signature)); + } + } + TEST_SPECIFICATIONS = testCaseList.toArray(new TestCase[0]); + } + + public static String loadPrivateKey(String keyFile) { + String privateKey = ""; + Path filePath = normalizePath(getFullFilePath(keyFile)); + try { + privateKey = new String(Files.readAllBytes(filePath), Charset.defaultCharset()); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load the private key " + filePath, ex); + } + return privateKey; + } + + private static String[] retrieveV2TestCases() { + File file = new File(MWSV2_TEST_CASE_PATH); + String[] directories = file.list(new FilenameFilter() { + @Override + public boolean accept(File current, String name) { + return new File(current, name).isDirectory(); + } + }); + return directories; + } + + private static byte[] loadTestData(String filePath) { + byte[] bytes = {}; + Path path = normalizePath(MWSV2_TEST_CASE_PATH + filePath); + try { + bytes = Files.readAllBytes(path); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load " + filePath, ex); + } + return bytes; + } + + private static String loadTestDataAsString(String filePath) { + byte[] bytes = loadTestData(filePath); + return bytes.length==0 ? "" : new String(bytes, Charset.defaultCharset()); + } + + private static UnsignedRequest loadUnsignedRequest(String filePath) { + UnsignedRequest unsignedRequest = new UnsignedRequest(); + byte[] jsonData = loadTestData(filePath); + try { + ObjectMapper objectMapper = new ObjectMapper(); + unsignedRequest = objectMapper.readValue(jsonData, UnsignedRequest.class); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load " + filePath, ex); + } + return unsignedRequest; + } + + private static byte[] getTestRequestBody(UnsignedRequest unsignedRequest, String caseName) { + byte[] bytes = new byte[0]; + if (unsignedRequest.getBodyFilepath() != null) { + String bodyFile = caseName.concat("/").concat(unsignedRequest.getBodyFilepath()); + bytes = ProtocolTestSuiteHelper.loadTestData(bodyFile); + } + else if (unsignedRequest.getHttpVerb() != null) { + bytes = unsignedRequest.getBody().getBytes(StandardCharsets.UTF_8); + } + return bytes; + } + + private static AuthenticationHeader loadAuthenticationHeader(String filePath) { + AuthenticationHeader authenticationHeader = new AuthenticationHeader(); + byte[] jsonData = loadTestData(filePath); + try { + ObjectMapper objectMapper = new ObjectMapper(); + authenticationHeader = objectMapper.readValue(jsonData, AuthenticationHeader.class); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load " + filePath, ex); + } + return authenticationHeader; + } + + private static Path normalizePath(String filepath) { + return Paths.get(filepath).normalize(); + } + + private static String getFullFilePath(String filePath) { + return TEST_SUITE_RELATIVE_PATH + filePath; + } +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationHeader.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationHeader.java new file mode 100644 index 00000000..42a82a15 --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationHeader.java @@ -0,0 +1,18 @@ +package com.mdsol.mauth.test.utils.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AuthenticationHeader { + @JsonProperty("MCC-Authentication") + String mccAuthentication; + @JsonProperty("MCC-Time") + Long mccTime; + + public String getMccAuthentication() { return mccAuthentication; } + + public void setMccAuthentication(String mccAuthentication) { this.mccAuthentication = mccAuthentication; } + + public long getMccTime() { return mccTime; } + + public void setMccTime(Long mccTime) { this.mccTime = mccTime; } +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationOnly.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationOnly.java new file mode 100644 index 00000000..5f0deab5 --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/AuthenticationOnly.java @@ -0,0 +1,30 @@ +package com.mdsol.mauth.test.utils.model; + +public class AuthenticationOnly implements TestCase { + String name; + AuthenticationHeader authHeader; + UnsignedRequest unsignedRequest; + + public AuthenticationOnly( + String name, + UnsignedRequest unsignedRequest, + AuthenticationHeader authHeader) { + this.name = name; + this.unsignedRequest = unsignedRequest; + this.authHeader = authHeader; + } + + @Override + public String getName() { return name; } + + @Override + public CaseType getType() { return CaseType.AUTHENTICATION_ONLY; } + + public UnsignedRequest getUnsignedRequest() { return unsignedRequest; } + + public void setUnsignedRequest(UnsignedRequest unsignedRequest) { this.unsignedRequest = unsignedRequest; } + + public AuthenticationHeader getAuthenticationHeader() { return authHeader; } + + public void setAuthenticationHeader(AuthenticationHeader authHeader) { this.authHeader = authHeader; } +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/CaseType.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/CaseType.java new file mode 100644 index 00000000..3a249717 --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/CaseType.java @@ -0,0 +1,6 @@ +package com.mdsol.mauth.test.utils.model; + +public enum CaseType { + AUTHENTICATION_ONLY, + SIGNING_AUTHENTICATION +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningAuthentication.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningAuthentication.java new file mode 100644 index 00000000..3612887c --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningAuthentication.java @@ -0,0 +1,44 @@ +package com.mdsol.mauth.test.utils.model; + +public class SigningAuthentication implements TestCase { + String name; + String stringToSign; + String signature; + AuthenticationHeader authHeader; + UnsignedRequest unsignedRequest; + + public SigningAuthentication( + String name, + UnsignedRequest unsignedRequest, + AuthenticationHeader authHeader, + String stringToSign, + String signature) { + this.name = name; + this.unsignedRequest = unsignedRequest; + this.authHeader = authHeader; + this.stringToSign = stringToSign; + this.signature = signature; + } + + @Override + public String getName() { return name; } + + @Override + public CaseType getType() { return CaseType.SIGNING_AUTHENTICATION; } + + public String getStringToSign() { return stringToSign; } + + public void setStringToSign(String stringToSign) { this.stringToSign = stringToSign; } + + public String getSignature() { return signature; } + + public void setSignature(String signature) { this.signature = signature; } + + public UnsignedRequest getUnsignedRequest() { return unsignedRequest; } + + public void setUnsignedRequest(UnsignedRequest unsignedRequest) { this.unsignedRequest = unsignedRequest; } + + public AuthenticationHeader getAuthenticationHeader() { return authHeader; } + + public void setAuthenticationHeader(AuthenticationHeader authHeader) { this.authHeader = authHeader; } +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningConfig.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningConfig.java new file mode 100644 index 00000000..dcda9a6d --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/SigningConfig.java @@ -0,0 +1,33 @@ +package com.mdsol.mauth.test.utils.model; + +import com.mdsol.mauth.test.utils.ProtocolTestSuiteHelper; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class SigningConfig { + @JsonProperty("app_uuid") + String appUuid; + @JsonProperty("request_time") + String requestTime; + @JsonProperty("private_key_file") + String privateKeyFile; + + String privateKey; + + public String getAppUuid() { return appUuid; } + + public void setAppUuid(String appUuid) { this.appUuid = appUuid; } + + public String getRequestTime() { return requestTime; } + + public void setRequestTime(String requestTime) { this.requestTime = requestTime; } + + public String getPrivateKeyFile() { return privateKeyFile; } + + public void setPrivateKeyFile(String privateKeyFile) { + this.privateKeyFile = privateKeyFile; + privateKey = ProtocolTestSuiteHelper.loadPrivateKey(privateKeyFile); + } + + public String getPrivateKey() { return privateKey; } +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/TestCase.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/TestCase.java new file mode 100644 index 00000000..26348522 --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/TestCase.java @@ -0,0 +1,6 @@ +package com.mdsol.mauth.test.utils.model; + +public interface TestCase { + String getName(); + CaseType getType(); +} diff --git a/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/UnsignedRequest.java b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/UnsignedRequest.java new file mode 100644 index 00000000..0d4afee2 --- /dev/null +++ b/modules/mauth-test-utils/src/main/java/com/mdsol/mauth/test/utils/model/UnsignedRequest.java @@ -0,0 +1,57 @@ +package com.mdsol.mauth.test.utils.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.nio.charset.StandardCharsets; + +public class UnsignedRequest { + @JsonProperty("verb") + String httpVerb; + @JsonProperty("url") + String urlString; + @JsonProperty("body_filepath") + String bodyFilepath; + @JsonProperty("body") + String body; + + String resourcePath; + String queryString; + byte[] bodyInBytes; + + public String getHttpVerb() { return httpVerb; } + + public void setHttpVerb(String httpVerb) { this.httpVerb = httpVerb; } + + public String getUrlString() { return urlString; } + + public void setUrlString(String urlString) { + this.urlString = urlString; + int idx = urlString.indexOf('?'); + if (idx == -1 || idx == urlString.length()-1) { + this.resourcePath = urlString; + this.queryString = ""; + } + else { + this.resourcePath = urlString.substring(0, idx); + this.queryString = urlString.substring(idx+1); + } + } + + public String getBody() { return body; } + + public void setBody(String body) { this.body = body == null? "" : body; } + + public byte[] getBodyInBytes() { return bodyInBytes; } + + public void setBodyInBytes(byte[] bodyInBytes) { + this.bodyInBytes = bodyInBytes == null? "".getBytes(StandardCharsets.UTF_8) : bodyInBytes; + } + + public String getBodyFilepath() { return bodyFilepath; } + + public void setBodyFilepath(String bodyFilepath) { this.bodyFilepath = bodyFilepath; } + + public String getResourcePath() { return resourcePath; } + + public String getQueryString() { return queryString; } +}