diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e09455c..43d1a61a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added Verify v2 API implementation - Added Advanced Machine Detection to Voice API - Fixed VbcEndpoint NCCO +- Removed dependency on `jakarta.xml.bind` +- Made `jakarta.servlet` an optional dependency +- Deprecated all methods and classes that use `javax.servlet.HttpServletRequest` # [7.3.0] - 2023-04-14 - Viber video message now requires setting duration and file size diff --git a/build.gradle b/build.gradle index 0b348c5ba..790ef2892 100644 --- a/build.gradle +++ b/build.gradle @@ -24,13 +24,13 @@ repositories { } dependencies { + compileOnly 'jakarta.servlet:jakarta.servlet-api:4.0.4' + implementation 'commons-codec:commons-codec:1.15' implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.0' implementation 'io.openapitools.jackson.dataformat:jackson-dataformat-hal:1.0.9' - implementation 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3' - implementation 'jakarta.servlet:jakarta.servlet-api:4.0.4' implementation 'com.vonage:jwt:1.0.2' testImplementation 'junit:junit:4.13.2' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c85a1f75..37aef8d3f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/vonage/client/auth/RequestSigning.java b/src/main/java/com/vonage/client/auth/RequestSigning.java index ad1ea16a2..c500f2312 100644 --- a/src/main/java/com/vonage/client/auth/RequestSigning.java +++ b/src/main/java/com/vonage/client/auth/RequestSigning.java @@ -25,6 +25,7 @@ import org.apache.http.message.BasicNameValuePair; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.time.Instant; @@ -154,9 +155,12 @@ protected static void constructSignatureForRequestParameters(List * @param secretKey The pre-shared secret key used by the sender of the request to create the signature. * * @return true if the signature is correct for this request and secret key. + * + * @deprecated This method will be removed in a future release due to the dependency on javax.servlet. */ + @Deprecated public static boolean verifyRequestSignature(HttpServletRequest request, String secretKey) { - return verifyRequestSignature(request, secretKey, System.currentTimeMillis()); + return verifyRequestSignature(request, secretKey, HashUtil.HashType.MD5); } /** @@ -167,48 +171,70 @@ public static boolean verifyRequestSignature(HttpServletRequest request, String * @param hashType Hash type to be used to construct request parameters. * * @return true if the signature is correct for this request and secret key. + * + * @deprecated This method will be removed in a future release due to the dependency on javax.servlet. */ + @Deprecated public static boolean verifyRequestSignature(HttpServletRequest request, String secretKey, HashUtil.HashType hashType) { - return verifyRequestSignature(request, secretKey, System.currentTimeMillis(), hashType); + try { + return verifyRequestSignature( + request.getContentType(), + request.getInputStream(), + request.getParameterMap(), + secretKey, System.currentTimeMillis(), hashType + ); + } + catch (IOException ex) { + throw new VonageUnexpectedException("Error encountered when opening input stream for request", ex); + } } /** - * Verifies the signature in an HttpServletRequest. - * Hashing strategy is MD5. + * Verifies the signature in an HttpServletRequest. Hashing strategy is MD5. * - * @param request The HttpServletRequest to be verified. + * @param contentType The request Content-Type header. + * @param inputStream The request data stream. + * @param parameterMap The request parameters. * @param secretKey The pre-shared secret key used by the sender of the request to create the signature. * @param currentTimeMillis The current time, in milliseconds. * * @return true if the signature is correct for this request and secret key. */ - protected static boolean verifyRequestSignature(HttpServletRequest request, + protected static boolean verifyRequestSignature(String contentType, + InputStream inputStream, + Map parameterMap, String secretKey, long currentTimeMillis) { - return verifyRequestSignature(request, secretKey, currentTimeMillis, HashUtil.HashType.MD5); + return verifyRequestSignature(contentType, inputStream, parameterMap, + secretKey, currentTimeMillis, HashUtil.HashType.MD5 + ); } /** * Verifies the signature in an HttpServletRequest. * - * @param request The HttpServletRequest to be verified. + * @param contentType The request Content-Type header. + * @param inputStream The request data stream. + * @param parameterMap The request parameters. * @param secretKey The pre-shared secret key used by the sender of the request to create the signature. * @param currentTimeMillis The current time, in milliseconds. * @param hashType Hash type to be used to construct request parameters. * * @return true if the signature is correct for this request and secret key. */ - protected static boolean verifyRequestSignature(HttpServletRequest request, + protected static boolean verifyRequestSignature(String contentType, + InputStream inputStream, + Map parameterMap, String secretKey, long currentTimeMillis, HashUtil.HashType hashType) { // Construct a sorted list of the name-value pair parameters supplied in the request, excluding the signature parameter Map sortedParams = new TreeMap<>(); - if (request.getContentType() != null && request.getContentType().equals(APPLICATION_JSON)) { + if (APPLICATION_JSON.equals(contentType) && inputStream != null) { ObjectMapper mapper = new ObjectMapper(); - try{ - Map params = mapper.readValue(request.getInputStream(), new TypeReference>(){}); + try { + Map params = mapper.readValue(inputStream, new TypeReference>(){}); for (Map.Entry entry : params.entrySet()) { String name = entry.getKey(); String value = entry.getValue(); @@ -224,11 +250,11 @@ protected static boolean verifyRequestSignature(HttpServletRequest request, } } else { - for (Map.Entry entry: request.getParameterMap().entrySet()) { + for (Map.Entry entry : parameterMap.entrySet()) { String name = entry.getKey(); String value = entry.getValue()[0]; log.info(name + " = " + value); - if (value == null || value.trim().equals("")) { + if (value == null || value.trim().isEmpty()) { continue; } sortedParams.put(name, value); diff --git a/src/main/java/com/vonage/client/sms/callback/AbstractMOServlet.java b/src/main/java/com/vonage/client/sms/callback/AbstractMOServlet.java index e2f8bdd83..35b0ba9a0 100644 --- a/src/main/java/com/vonage/client/sms/callback/AbstractMOServlet.java +++ b/src/main/java/com/vonage/client/sms/callback/AbstractMOServlet.java @@ -40,8 +40,11 @@ * Note: This servlet will immediately ack the callback as soon as it is validated. Your subclass will * consume the callback object asynchronously. This is because it is important to keep latency of * the acknowledgement to a minimum in order to maintain throughput when operating at any sort of volume. - * You are responsible for persisting this object in the event of any failure whilst processing + * You are responsible for persisting this object in the event of any failure whilst processing. + * + * @deprecated This class is no longer maintained and may be removed in a future release. */ +@Deprecated public abstract class AbstractMOServlet extends HttpServlet { private static final long serialVersionUID = 8745764381059238419L; diff --git a/src/main/java/com/vonage/client/voice/ncco/package-info.java b/src/main/java/com/vonage/client/voice/ncco/package-info.java index cc65665d2..3b6e39158 100644 --- a/src/main/java/com/vonage/client/voice/ncco/package-info.java +++ b/src/main/java/com/vonage/client/voice/ncco/package-info.java @@ -17,9 +17,5 @@ /** * Provides useful NCCO classes which can be serialized using Jackson when * implementing webhooks to drive the Vonage Voice API. - *

- * The simplest way to use these classes is to subclass {@link com.vonage.client.voice.servlet.AbstractAnswerServlet} - * and implement {@code handleRequest(javax.servlet.http.HttpServletRequest)}. - * the returned NCCOResponse will automatically be serialized correctly. */ package com.vonage.client.voice.ncco; \ No newline at end of file diff --git a/src/main/java/com/vonage/client/voice/servlet/AbstractAnswerServlet.java b/src/main/java/com/vonage/client/voice/servlet/AbstractAnswerServlet.java index 684245ff9..461d789bc 100644 --- a/src/main/java/com/vonage/client/voice/servlet/AbstractAnswerServlet.java +++ b/src/main/java/com/vonage/client/voice/servlet/AbstractAnswerServlet.java @@ -28,7 +28,10 @@ * Implement {@link #handleRequest(HttpServletRequest)} to return an {@link NccoResponse} and this servlet will * ensure that the response is serialized correctly for the Vonage Voice API. *

+ * + * @deprecated This class is no longer maintained and may be removed in a future release. */ +@Deprecated public abstract class AbstractAnswerServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { diff --git a/src/main/java/com/vonage/client/voice/servlet/package-info.java b/src/main/java/com/vonage/client/voice/servlet/package-info.java index c9477a1fa..700a2d085 100644 --- a/src/main/java/com/vonage/client/voice/servlet/package-info.java +++ b/src/main/java/com/vonage/client/voice/servlet/package-info.java @@ -16,5 +16,7 @@ /** * Provides an abstract Servlet for building NCCO webhooks for the Vonage Voice API. + * + * @deprecated This package is no longer maintained and may be removed or substantially refactored in a future release. */ package com.vonage.client.voice.servlet; \ No newline at end of file diff --git a/src/test/java/com/vonage/client/auth/RequestSigningTest.java b/src/test/java/com/vonage/client/auth/RequestSigningTest.java index 99c70024a..6652c4beb 100644 --- a/src/test/java/com/vonage/client/auth/RequestSigningTest.java +++ b/src/test/java/com/vonage/client/auth/RequestSigningTest.java @@ -132,8 +132,10 @@ private static Map constructParamMap(List params) @Test public void testVerifyRequestSignature() { - HttpServletRequest request = constructDummyRequest(); - assertTrue(RequestSigning.verifyRequestSignature(request, "abcde", 2100000)); + assertTrue(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, constructDummyParams(), + "abcde", 2100000 + )); } @Test @@ -141,13 +143,19 @@ public void testVerifyRequestSignatureWithSha1Hash() { Map params = constructDummyParams(); params.put("sig", new String[]{"b7f749de27b4adcf736cc95c9a7e059a16c85127"}); - assertTrue(RequestSigning.verifyRequestSignature(constructDummyRequest(params), "abcde", 2100000, HashUtil.HashType.HMAC_SHA1)); + assertTrue(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, params, + "abcde", 2100000, HashUtil.HashType.HMAC_SHA1 + )); } @Test - public void testVerifySignatureRequestJson(){ - HttpServletRequest request = ConstructDummyRequestJson(); - assertTrue(RequestSigning.verifyRequestSignature(request,"abcde",2100000, HashUtil.HashType.HMAC_SHA1)); + public void testVerifySignatureRequestJson() throws Exception { + HttpServletRequest request = constructDummyRequestJson(); + assertTrue(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, request.getInputStream(), constructDummyParams(), + "abcde", 2100000, HashUtil.HashType.HMAC_SHA1 + )); } @Test @@ -155,15 +163,20 @@ public void testVerifyRequestSignatureWithHmacSha256Hash() { Map params = constructDummyParams(); params.put("sig", new String[]{"8d1b0428276b6a070578225914c3502cc0687a454dfbbbb370c76a14234cb546"}); - assertTrue(RequestSigning.verifyRequestSignature(constructDummyRequest(params), "abcde", 2100000, HashUtil.HashType.HMAC_SHA256)); + assertTrue(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, params, + "abcde", 2100000, HashUtil.HashType.HMAC_SHA256 + )); } @Test - public void testVerifyRequestSignatureWithHmacMd5Hash() { + public void testVerifyRequestSignatureWithHmacMd5Hash() throws Exception { Map params = constructDummyParams(); params.put("sig", new String[]{"e0afe267aefd6dd18a848c1681517a19"}); - - assertTrue(RequestSigning.verifyRequestSignature(constructDummyRequest(params), "abcde", 2100000, HashUtil.HashType.HMAC_MD5)); + assertTrue(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, params, + "abcde", 2100000, HashUtil.HashType.HMAC_MD5 + )); } @Test @@ -171,28 +184,34 @@ public void testVerifyRequestSignatureWithHmacSha512Hash() { Map params = constructDummyParams(); params.put("sig", new String[]{"1c834a1f6a377d4473971387b065cb38e2ad6c4869ba77b7b53e207a344e87ba04b456dfc697b371a2d1ce476d01dafd4394aa97525eff23badad39d2389a710"}); - assertTrue(RequestSigning.verifyRequestSignature(constructDummyRequest(params), "abcde", 2100000, HashUtil.HashType.HMAC_SHA512)); + assertTrue(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, params, + "abcde", 2100000, HashUtil.HashType.HMAC_SHA512 + )); } @Test public void testVerifyRequestSignatureNoSig() { - HttpServletRequest request = constructDummyRequest(constructDummyParamsNoSignature()); - - assertFalse(RequestSigning.verifyRequestSignature(request, "abcde", 2100000)); + assertFalse(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, constructDummyParamsNoSignature(), + "abcde", 2100000 + )); } @Test public void testVerifyRequestSignatureBadTimestamp() { - HttpServletRequest request = constructDummyRequest(constructDummyParamsInvalidTimestamp()); - - assertFalse(RequestSigning.verifyRequestSignature(request, "abcde", 2100000)); + assertFalse(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, constructDummyParamsInvalidTimestamp(), + "abcde", 2100000 + )); } @Test public void testVerifyRequestSignatureMissingTimestamp() { - HttpServletRequest request = constructDummyRequest(constructDummyParamsNoTimestamp()); - - assertFalse(RequestSigning.verifyRequestSignature(request, "abcde", 2100000)); + assertFalse(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, constructDummyParamsNoTimestamp(), + "abcde", 2100000 + )); } @Test @@ -201,9 +220,10 @@ public void testVerifyRequestSignatureHandlesNullParams() { params.put("b", new String[]{ null }); params.put("sig", new String[]{"a3368bf718ba104dcb392d8877e8eb2b"}); - HttpServletRequest request = constructDummyRequest(params); - - assertTrue(RequestSigning.verifyRequestSignature(request, "abcde", 2100000)); + assertTrue(RequestSigning.verifyRequestSignature( + RequestSigning.APPLICATION_JSON, null, params, + "abcde", 2100000 + )); } private HttpServletRequest constructDummyRequest() { @@ -211,7 +231,7 @@ private HttpServletRequest constructDummyRequest() { } - private HttpServletRequest ConstructDummyRequestJson() { + private HttpServletRequest constructDummyRequestJson() { MockHttpServletRequest request = new MockHttpServletRequest(); String dummyJson = "{\"a\":\"alphabet\",\"b\":\"bananas\",\"timestamp\":\"2100\",\"sig\":\"b7f749de27b4adcf736cc95c9a7e059a16c85127\"}"; request.setContent(dummyJson.getBytes());