Skip to content

Commit

Permalink
fix: Call modification & signature verification (#498)
Browse files Browse the repository at this point in the history
QoL update for consistency
  • Loading branch information
SMadani authored Nov 29, 2023
1 parent 27f86d1 commit 1de01a9
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 59 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

# [8.0.0] - 2023-11-??
# [8.0.0] - 2023-11-30
- Added `redirect_url` parameter to `SilentAuthWorkflow`
- Bumped Jackson version to 2.16.0
- Use String instead of UUID in `VoiceClient` call modification methods
- Added public `verifyRequestSignature` method to `RequestSigning`

# [8.0.0-rc2] - 2023-11-07
- Removed packages:
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/vonage/client/auth/RequestSigning.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,25 @@ protected static void constructSignatureForRequestParameters(List<NameValuePair>
params.add(new BasicNameValuePair(PARAM_SIGNATURE, hashed));
}

/**
* Verifies the signature in an HttpServletRequest. Hashing strategy is MD5.
*
* @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.
*
* @return true if the signature is correct for this request and secret key.
*
* @since 8.0.0
*/
public static boolean verifyRequestSignature(InputStream inputStream,
String contentType,
Map<String, String[]> parameterMap,
String secretKey) {
return verifyRequestSignature(contentType, inputStream, parameterMap, secretKey, System.currentTimeMillis());
}

/**
* Verifies the signature in an HttpServletRequest. Hashing strategy is MD5.
*
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/vonage/client/voice/CallEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,52 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.vonage.client.Jsonable;

/**
* Represents metadata about a call.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class CallEvent implements Jsonable {
private String uuid, conversationUuid;
private CallStatus status;
private CallDirection direction;

/**
* The unique identifier for this call leg. The UUID is created when your call request is accepted by Vonage.
* You use the UUID in all requests for individual live calls.
*
* @return The call ID.
*/
@JsonProperty("uuid")
public String getUuid() {
return uuid;
}

/**
* The unique identifier for the conversation this call leg is part of.
*
* @return The conversation ID as a string.
*/
@JsonProperty("conversation_uuid")
public String getConversationUuid() {
return conversationUuid;
}

/**
* The status of the call.
*
* @return The call's status as an enum.
*/
@JsonProperty("status")
public CallStatus getStatus() {
return status;
}

/**
* Whether the call is inbound or outbound.
*
* @return The call direction as an enum.
*/
@JsonProperty("direction")
public CallDirection getDirection() {
return direction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ class TransferDestination {
this(Type.NCCO, null, ncco);
}

public TransferDestination(Type type, String url) {
this(type, url, null);
}

public TransferDestination(Type type, String url, Ncco ncco) {
this.type = type;
this.urls = url != null ? new String[]{url} : null;
Expand Down
19 changes: 9 additions & 10 deletions src/main/java/com/vonage/client/voice/VoiceClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;

/**
Expand Down Expand Up @@ -142,8 +141,8 @@ public CallInfoPage listCalls(CallsFilter filter) throws VonageResponseParseExce
/**
* Look up the status of a single call initiated by {@link #createCall(Call)}.
*
* @param uuid (required) The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This
* value can be obtained with {@link CallEvent#getUuid()}.
* @param uuid (required) The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
* This value can be obtained with {@link CallEvent#getUuid()}.
*
* @return A CallInfo object, representing the response from the Vonage Voice API.
*
Expand Down Expand Up @@ -172,8 +171,8 @@ public DtmfResponse sendDtmf(String uuid, String digits) throws VonageResponsePa
return sendDtmf.execute(new DtmfPayload(digits, validateUuid(uuid)));
}

private void modifyCall(UUID callId, ModifyCallAction action) throws VoiceResponseException {
modifyCall.execute(new ModifyCallPayload(action, callId.toString()));
private void modifyCall(String callId, ModifyCallAction action) throws VoiceResponseException {
modifyCall.execute(new ModifyCallPayload(action, validateUuid(callId)));
}

/**
Expand All @@ -186,7 +185,7 @@ private void modifyCall(UUID callId, ModifyCallAction action) throws VoiceRespon
*
* @since 7.11.0
*/
public void earmuffCall(UUID callId) throws VoiceResponseException {
public void earmuffCall(String callId) throws VoiceResponseException {
modifyCall(callId, ModifyCallAction.EARMUFF);
}

Expand All @@ -200,7 +199,7 @@ public void earmuffCall(UUID callId) throws VoiceResponseException {
*
* @since 7.11.0
*/
public void unearmuffCall(UUID callId) throws VoiceResponseException {
public void unearmuffCall(String callId) throws VoiceResponseException {
modifyCall(callId, ModifyCallAction.UNEARMUFF);
}

Expand All @@ -213,7 +212,7 @@ public void unearmuffCall(UUID callId) throws VoiceResponseException {
*
* @since 7.11.0
*/
public void muteCall(UUID callId) throws VoiceResponseException {
public void muteCall(String callId) throws VoiceResponseException {
modifyCall(callId, ModifyCallAction.MUTE);
}

Expand All @@ -226,7 +225,7 @@ public void muteCall(UUID callId) throws VoiceResponseException {
*
* @since 7.11.0
*/
public void unmuteCall(UUID callId) throws VoiceResponseException {
public void unmuteCall(String callId) throws VoiceResponseException {
modifyCall(callId, ModifyCallAction.UNMUTE);
}

Expand All @@ -239,7 +238,7 @@ public void unmuteCall(UUID callId) throws VoiceResponseException {
*
* @since 7.11.0
*/
public void terminateCall(UUID callId) throws VoiceResponseException {
public void terminateCall(String callId) throws VoiceResponseException {
modifyCall(callId, ModifyCallAction.HANGUP);
}

Expand Down
61 changes: 27 additions & 34 deletions src/test/java/com/vonage/client/auth/RequestSigningTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.vonage.client.auth;

import static com.vonage.client.auth.RequestSigning.*;
import com.vonage.client.auth.hashutils.HashUtil;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
Expand All @@ -39,13 +40,13 @@ public void testConstructSignatureForRequestParameters() {
params.add(new BasicNameValuePair("a", "alphabet"));
params.add(new BasicNameValuePair("b", "bananas"));

RequestSigning.constructSignatureForRequestParameters(params, "abcde", 2100);
constructSignatureForRequestParameters(params, "abcde", 2100);
Map<String, String> paramMap = constructParamMap(params);
// md5 -s "&a=alphabet&b=bananas&timestamp=2100abcde"
String expected = "7d43241108912b32cc315b48ce681acf";

assertEquals(expected, paramMap.get(RequestSigning.PARAM_SIGNATURE));
RequestSigning.constructSignatureForRequestParameters(params, "abcde");
constructSignatureForRequestParameters(params, "abcde");
paramMap = constructParamMap(params);
assertNotEquals(expected, paramMap.get(RequestSigning.PARAM_SIGNATURE));
}
Expand All @@ -56,7 +57,7 @@ public void testConstructSignatureForRequestParametersWithSha1Hash() {
params.add(new BasicNameValuePair("a", "alphabet"));
params.add(new BasicNameValuePair("b", "bananas"));

RequestSigning.constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_SHA1);
constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_SHA1);
Map<String, String> paramMap = constructParamMap(params);
// md5 -s "&a=alphabet&b=bananas&timestamp=2100"
assertEquals("b7f749de27b4adcf736cc95c9a7e059a16c85127", paramMap.get(RequestSigning.PARAM_SIGNATURE));
Expand All @@ -68,7 +69,7 @@ public void testConstructSignatureForRequestParametersWithHmacMd5Hash() {
params.add(new BasicNameValuePair("a", "alphabet"));
params.add(new BasicNameValuePair("b", "bananas"));

RequestSigning.constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_MD5);
constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_MD5);
Map<String, String> paramMap = constructParamMap(params);
// md5 -s "&a=alphabet&b=bananas&timestamp=2100"
assertEquals("e0afe267aefd6dd18a848c1681517a19", paramMap.get(RequestSigning.PARAM_SIGNATURE));
Expand All @@ -80,7 +81,7 @@ public void testConstructSignatureForRequestParametersWithHmacSha256Hash() {
params.add(new BasicNameValuePair("a", "alphabet"));
params.add(new BasicNameValuePair("b", "bananas"));

RequestSigning.constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_SHA256);
constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_SHA256);
Map<String, String> paramMap = constructParamMap(params);
// md5 -s "&a=alphabet&b=bananas&timestamp=2100"
assertEquals("8d1b0428276b6a070578225914c3502cc0687a454dfbbbb370c76a14234cb546", paramMap.get(RequestSigning.PARAM_SIGNATURE));
Expand All @@ -92,7 +93,7 @@ public void testConstructSignatureForRequestParametersWithHmacSha512Hash() {
params.add(new BasicNameValuePair("a", "alphabet"));
params.add(new BasicNameValuePair("b", "bananas"));

RequestSigning.constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_SHA512);
constructSignatureForRequestParameters(params, "abcde", 2100, HashUtil.HashType.HMAC_SHA512);
Map<String, String> paramMap = constructParamMap(params);
// md5 -s "&a=alphabet&b=bananas&timestamp=2100"
assertEquals("1c834a1f6a377d4473971387b065cb38e2ad6c4869ba77b7b53e207a344e87ba04b456dfc697b371a2d1ce476d01dafd4394aa97525eff23badad39d2389a710", paramMap.get(RequestSigning.PARAM_SIGNATURE));
Expand All @@ -106,7 +107,7 @@ public void testConstructSignatureForRequestParametersSkipsSignature() {
params.add(new BasicNameValuePair("sig", "7d43241108912b32cc315b48ce681acf"));


RequestSigning.constructSignatureForRequestParameters(params, "abcde", 2100);
constructSignatureForRequestParameters(params, "abcde", 2100);
Map<String, String> paramMap = constructParamMap(params);
// md5 -s "&a=alphabet&b=bananas&timestamp=2100abcde"
assertEquals("7d43241108912b32cc315b48ce681acf", paramMap.get(RequestSigning.PARAM_SIGNATURE));
Expand All @@ -118,7 +119,7 @@ public void testConstructSignatureForRequestParametersSkipsNullValues() {
params.add(new BasicNameValuePair("a", "alphabet"));
params.add(new BasicNameValuePair("b", null));

RequestSigning.constructSignatureForRequestParameters(params, "abcde", 2100);
constructSignatureForRequestParameters(params, "abcde", 2100);
Map<String, String> paramMap = constructParamMap(params);
// md5 -s "&a=alphabet&timestamp=2100abcde"
assertEquals("a3368bf718ba104dcb392d8877e8eb2b", paramMap.get(RequestSigning.PARAM_SIGNATURE));
Expand All @@ -134,18 +135,15 @@ private static Map<String, String> constructParamMap(List<NameValuePair> params)

@Test
public void testVerifyRequestSignature() {
assertTrue(RequestSigning.verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, constructDummyParams(),
"abcde", 2100000
));
assertTrue(verifySignature(constructDummyParams()));
}

@Test
public void testVerifyRequestSignatureWithSha1Hash() {
Map<String, String[]> params = constructDummyParams();
params.put("sig", new String[]{"b7f749de27b4adcf736cc95c9a7e059a16c85127"});

assertTrue(RequestSigning.verifyRequestSignature(
assertTrue(verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, params,
"abcde", 2100000, HashUtil.HashType.HMAC_SHA1
));
Expand All @@ -154,7 +152,7 @@ public void testVerifyRequestSignatureWithSha1Hash() {
@Test
public void testVerifySignatureRequestJson() throws Exception {
HttpServletRequest request = constructDummyRequestJson();
assertTrue(RequestSigning.verifyRequestSignature(
assertTrue(verifyRequestSignature(
RequestSigning.APPLICATION_JSON, request.getInputStream(), constructDummyParams(),
"abcde", 2100000, HashUtil.HashType.HMAC_SHA1
));
Expand All @@ -165,7 +163,7 @@ public void testVerifyRequestSignatureWithHmacSha256Hash() {
Map<String, String[]> params = constructDummyParams();
params.put("sig", new String[]{"8d1b0428276b6a070578225914c3502cc0687a454dfbbbb370c76a14234cb546"});

assertTrue(RequestSigning.verifyRequestSignature(
assertTrue(verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, params,
"abcde", 2100000, HashUtil.HashType.HMAC_SHA256
));
Expand All @@ -175,7 +173,7 @@ public void testVerifyRequestSignatureWithHmacSha256Hash() {
public void testVerifyRequestSignatureWithHmacMd5Hash() throws Exception {
Map<String, String[]> params = constructDummyParams();
params.put("sig", new String[]{"e0afe267aefd6dd18a848c1681517a19"});
assertTrue(RequestSigning.verifyRequestSignature(
assertTrue(verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, params,
"abcde", 2100000, HashUtil.HashType.HMAC_MD5
));
Expand All @@ -186,53 +184,44 @@ public void testVerifyRequestSignatureWithHmacSha512Hash() {
Map<String, String[]> params = constructDummyParams();
params.put("sig", new String[]{"1c834a1f6a377d4473971387b065cb38e2ad6c4869ba77b7b53e207a344e87ba04b456dfc697b371a2d1ce476d01dafd4394aa97525eff23badad39d2389a710"});

assertTrue(RequestSigning.verifyRequestSignature(
assertTrue(verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, params,
"abcde", 2100000, HashUtil.HashType.HMAC_SHA512
));
}

@Test
public void testVerifyRequestSignatureNoSig() {
assertFalse(RequestSigning.verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, constructDummyParamsNoSignature(),
"abcde", 2100000
));
assertFalse(verifySignature(constructDummyParamsNoSignature()));
}

@Test
public void testVerifyRequestSignatureBadTimestamp() {
assertFalse(RequestSigning.verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, constructDummyParamsInvalidTimestamp(),
"abcde", 2100000
));
assertFalse(verifySignature(constructDummyParamsInvalidTimestamp()));
}

@Test
public void testVerifyRequestSignatureMissingTimestamp() {
assertFalse(RequestSigning.verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, constructDummyParamsNoTimestamp(),
"abcde", 2100000
));
assertFalse(verifySignature(constructDummyParamsNoTimestamp()));
}

@Test
public void testVerifyRequestSignatureHandlesNullParams() {
Map<String, String[]> params = constructDummyParams();
params.put("b", new String[]{ null });
params.put("sig", new String[]{"a3368bf718ba104dcb392d8877e8eb2b"});
assertTrue(verifySignature(params));
}

assertTrue(RequestSigning.verifyRequestSignature(
RequestSigning.APPLICATION_JSON, null, params,
"abcde", 2100000
));
@Test
public void testVerifyRequestSignatureCurrentTimeMillis() {
assertFalse(verifyRequestSignature(null, APPLICATION_JSON, constructDummyParams(), "abcde"));
}

private HttpServletRequest constructDummyRequest() {
return constructDummyRequest(null);
}


private HttpServletRequest constructDummyRequestJson() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
String dummyJson = "{\"a\":\"alphabet\",\"b\":\"bananas\",\"timestamp\":\"2100\",\"sig\":\"b7f749de27b4adcf736cc95c9a7e059a16c85127\"}";
Expand Down Expand Up @@ -325,4 +314,8 @@ private HttpServletRequest constructDummyRequest(final Map<String, String[]> nul

return request;
}

private static boolean verifySignature(Map<String, String[]> params) {
return verifyRequestSignature(APPLICATION_JSON, null, params, "abcde", 2100000);
}
}
Loading

0 comments on commit 1de01a9

Please sign in to comment.