Skip to content

Commit 1de01a9

Browse files
authored
fix: Call modification & signature verification (#498)
QoL update for consistency
1 parent 27f86d1 commit 1de01a9

File tree

7 files changed

+92
-59
lines changed

7 files changed

+92
-59
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

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

1113
# [8.0.0-rc2] - 2023-11-07
1214
- Removed packages:

src/main/java/com/vonage/client/auth/RequestSigning.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,25 @@ protected static void constructSignatureForRequestParameters(List<NameValuePair>
146146
params.add(new BasicNameValuePair(PARAM_SIGNATURE, hashed));
147147
}
148148

149+
/**
150+
* Verifies the signature in an HttpServletRequest. Hashing strategy is MD5.
151+
*
152+
* @param contentType The request Content-Type header.
153+
* @param inputStream The request data stream.
154+
* @param parameterMap The request parameters.
155+
* @param secretKey The pre-shared secret key used by the sender of the request to create the signature.
156+
*
157+
* @return true if the signature is correct for this request and secret key.
158+
*
159+
* @since 8.0.0
160+
*/
161+
public static boolean verifyRequestSignature(InputStream inputStream,
162+
String contentType,
163+
Map<String, String[]> parameterMap,
164+
String secretKey) {
165+
return verifyRequestSignature(contentType, inputStream, parameterMap, secretKey, System.currentTimeMillis());
166+
}
167+
149168
/**
150169
* Verifies the signature in an HttpServletRequest. Hashing strategy is MD5.
151170
*

src/main/java/com/vonage/client/voice/CallEvent.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,52 @@
2020
import com.fasterxml.jackson.annotation.JsonProperty;
2121
import com.vonage.client.Jsonable;
2222

23+
/**
24+
* Represents metadata about a call.
25+
*/
2326
@JsonInclude(JsonInclude.Include.NON_NULL)
2427
@JsonIgnoreProperties(ignoreUnknown = true)
2528
public class CallEvent implements Jsonable {
2629
private String uuid, conversationUuid;
2730
private CallStatus status;
2831
private CallDirection direction;
2932

33+
/**
34+
* The unique identifier for this call leg. The UUID is created when your call request is accepted by Vonage.
35+
* You use the UUID in all requests for individual live calls.
36+
*
37+
* @return The call ID.
38+
*/
3039
@JsonProperty("uuid")
3140
public String getUuid() {
3241
return uuid;
3342
}
3443

44+
/**
45+
* The unique identifier for the conversation this call leg is part of.
46+
*
47+
* @return The conversation ID as a string.
48+
*/
3549
@JsonProperty("conversation_uuid")
3650
public String getConversationUuid() {
3751
return conversationUuid;
3852
}
3953

54+
/**
55+
* The status of the call.
56+
*
57+
* @return The call's status as an enum.
58+
*/
4059
@JsonProperty("status")
4160
public CallStatus getStatus() {
4261
return status;
4362
}
4463

64+
/**
65+
* Whether the call is inbound or outbound.
66+
*
67+
* @return The call direction as an enum.
68+
*/
4569
@JsonProperty("direction")
4670
public CallDirection getDirection() {
4771
return direction;

src/main/java/com/vonage/client/voice/TransferDestination.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@ class TransferDestination {
3434
this(Type.NCCO, null, ncco);
3535
}
3636

37-
public TransferDestination(Type type, String url) {
38-
this(type, url, null);
39-
}
40-
4137
public TransferDestination(Type type, String url, Ncco ncco) {
4238
this.type = type;
4339
this.urls = url != null ? new String[]{url} : null;

src/main/java/com/vonage/client/voice/VoiceClient.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.nio.file.Files;
2626
import java.nio.file.Path;
2727
import java.util.Objects;
28-
import java.util.UUID;
2928
import java.util.function.Function;
3029

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

175-
private void modifyCall(UUID callId, ModifyCallAction action) throws VoiceResponseException {
176-
modifyCall.execute(new ModifyCallPayload(action, callId.toString()));
174+
private void modifyCall(String callId, ModifyCallAction action) throws VoiceResponseException {
175+
modifyCall.execute(new ModifyCallPayload(action, validateUuid(callId)));
177176
}
178177

179178
/**
@@ -186,7 +185,7 @@ private void modifyCall(UUID callId, ModifyCallAction action) throws VoiceRespon
186185
*
187186
* @since 7.11.0
188187
*/
189-
public void earmuffCall(UUID callId) throws VoiceResponseException {
188+
public void earmuffCall(String callId) throws VoiceResponseException {
190189
modifyCall(callId, ModifyCallAction.EARMUFF);
191190
}
192191

@@ -200,7 +199,7 @@ public void earmuffCall(UUID callId) throws VoiceResponseException {
200199
*
201200
* @since 7.11.0
202201
*/
203-
public void unearmuffCall(UUID callId) throws VoiceResponseException {
202+
public void unearmuffCall(String callId) throws VoiceResponseException {
204203
modifyCall(callId, ModifyCallAction.UNEARMUFF);
205204
}
206205

@@ -213,7 +212,7 @@ public void unearmuffCall(UUID callId) throws VoiceResponseException {
213212
*
214213
* @since 7.11.0
215214
*/
216-
public void muteCall(UUID callId) throws VoiceResponseException {
215+
public void muteCall(String callId) throws VoiceResponseException {
217216
modifyCall(callId, ModifyCallAction.MUTE);
218217
}
219218

@@ -226,7 +225,7 @@ public void muteCall(UUID callId) throws VoiceResponseException {
226225
*
227226
* @since 7.11.0
228227
*/
229-
public void unmuteCall(UUID callId) throws VoiceResponseException {
228+
public void unmuteCall(String callId) throws VoiceResponseException {
230229
modifyCall(callId, ModifyCallAction.UNMUTE);
231230
}
232231

@@ -239,7 +238,7 @@ public void unmuteCall(UUID callId) throws VoiceResponseException {
239238
*
240239
* @since 7.11.0
241240
*/
242-
public void terminateCall(UUID callId) throws VoiceResponseException {
241+
public void terminateCall(String callId) throws VoiceResponseException {
243242
modifyCall(callId, ModifyCallAction.HANGUP);
244243
}
245244

src/test/java/com/vonage/client/auth/RequestSigningTest.java

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.vonage.client.auth;
1717

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

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

4748
assertEquals(expected, paramMap.get(RequestSigning.PARAM_SIGNATURE));
48-
RequestSigning.constructSignatureForRequestParameters(params, "abcde");
49+
constructSignatureForRequestParameters(params, "abcde");
4950
paramMap = constructParamMap(params);
5051
assertNotEquals(expected, paramMap.get(RequestSigning.PARAM_SIGNATURE));
5152
}
@@ -56,7 +57,7 @@ public void testConstructSignatureForRequestParametersWithSha1Hash() {
5657
params.add(new BasicNameValuePair("a", "alphabet"));
5758
params.add(new BasicNameValuePair("b", "bananas"));
5859

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

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

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

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

108109

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

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

135136
@Test
136137
public void testVerifyRequestSignature() {
137-
assertTrue(RequestSigning.verifyRequestSignature(
138-
RequestSigning.APPLICATION_JSON, null, constructDummyParams(),
139-
"abcde", 2100000
140-
));
138+
assertTrue(verifySignature(constructDummyParams()));
141139
}
142140

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

148-
assertTrue(RequestSigning.verifyRequestSignature(
146+
assertTrue(verifyRequestSignature(
149147
RequestSigning.APPLICATION_JSON, null, params,
150148
"abcde", 2100000, HashUtil.HashType.HMAC_SHA1
151149
));
@@ -154,7 +152,7 @@ public void testVerifyRequestSignatureWithSha1Hash() {
154152
@Test
155153
public void testVerifySignatureRequestJson() throws Exception {
156154
HttpServletRequest request = constructDummyRequestJson();
157-
assertTrue(RequestSigning.verifyRequestSignature(
155+
assertTrue(verifyRequestSignature(
158156
RequestSigning.APPLICATION_JSON, request.getInputStream(), constructDummyParams(),
159157
"abcde", 2100000, HashUtil.HashType.HMAC_SHA1
160158
));
@@ -165,7 +163,7 @@ public void testVerifyRequestSignatureWithHmacSha256Hash() {
165163
Map<String, String[]> params = constructDummyParams();
166164
params.put("sig", new String[]{"8d1b0428276b6a070578225914c3502cc0687a454dfbbbb370c76a14234cb546"});
167165

168-
assertTrue(RequestSigning.verifyRequestSignature(
166+
assertTrue(verifyRequestSignature(
169167
RequestSigning.APPLICATION_JSON, null, params,
170168
"abcde", 2100000, HashUtil.HashType.HMAC_SHA256
171169
));
@@ -175,7 +173,7 @@ public void testVerifyRequestSignatureWithHmacSha256Hash() {
175173
public void testVerifyRequestSignatureWithHmacMd5Hash() throws Exception {
176174
Map<String, String[]> params = constructDummyParams();
177175
params.put("sig", new String[]{"e0afe267aefd6dd18a848c1681517a19"});
178-
assertTrue(RequestSigning.verifyRequestSignature(
176+
assertTrue(verifyRequestSignature(
179177
RequestSigning.APPLICATION_JSON, null, params,
180178
"abcde", 2100000, HashUtil.HashType.HMAC_MD5
181179
));
@@ -186,53 +184,44 @@ public void testVerifyRequestSignatureWithHmacSha512Hash() {
186184
Map<String, String[]> params = constructDummyParams();
187185
params.put("sig", new String[]{"1c834a1f6a377d4473971387b065cb38e2ad6c4869ba77b7b53e207a344e87ba04b456dfc697b371a2d1ce476d01dafd4394aa97525eff23badad39d2389a710"});
188186

189-
assertTrue(RequestSigning.verifyRequestSignature(
187+
assertTrue(verifyRequestSignature(
190188
RequestSigning.APPLICATION_JSON, null, params,
191189
"abcde", 2100000, HashUtil.HashType.HMAC_SHA512
192190
));
193191
}
194192

195193
@Test
196194
public void testVerifyRequestSignatureNoSig() {
197-
assertFalse(RequestSigning.verifyRequestSignature(
198-
RequestSigning.APPLICATION_JSON, null, constructDummyParamsNoSignature(),
199-
"abcde", 2100000
200-
));
195+
assertFalse(verifySignature(constructDummyParamsNoSignature()));
201196
}
202197

203198
@Test
204199
public void testVerifyRequestSignatureBadTimestamp() {
205-
assertFalse(RequestSigning.verifyRequestSignature(
206-
RequestSigning.APPLICATION_JSON, null, constructDummyParamsInvalidTimestamp(),
207-
"abcde", 2100000
208-
));
200+
assertFalse(verifySignature(constructDummyParamsInvalidTimestamp()));
209201
}
210202

211203
@Test
212204
public void testVerifyRequestSignatureMissingTimestamp() {
213-
assertFalse(RequestSigning.verifyRequestSignature(
214-
RequestSigning.APPLICATION_JSON, null, constructDummyParamsNoTimestamp(),
215-
"abcde", 2100000
216-
));
205+
assertFalse(verifySignature(constructDummyParamsNoTimestamp()));
217206
}
218207

219208
@Test
220209
public void testVerifyRequestSignatureHandlesNullParams() {
221210
Map<String, String[]> params = constructDummyParams();
222211
params.put("b", new String[]{ null });
223212
params.put("sig", new String[]{"a3368bf718ba104dcb392d8877e8eb2b"});
213+
assertTrue(verifySignature(params));
214+
}
224215

225-
assertTrue(RequestSigning.verifyRequestSignature(
226-
RequestSigning.APPLICATION_JSON, null, params,
227-
"abcde", 2100000
228-
));
216+
@Test
217+
public void testVerifyRequestSignatureCurrentTimeMillis() {
218+
assertFalse(verifyRequestSignature(null, APPLICATION_JSON, constructDummyParams(), "abcde"));
229219
}
230220

231221
private HttpServletRequest constructDummyRequest() {
232222
return constructDummyRequest(null);
233223
}
234224

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

326315
return request;
327316
}
317+
318+
private static boolean verifySignature(Map<String, String[]> params) {
319+
return verifyRequestSignature(APPLICATION_JSON, null, params, "abcde", 2100000);
320+
}
328321
}

0 commit comments

Comments
 (0)