Skip to content

Commit 20a44ea

Browse files
committed
fixes issue with base64 decoding twice when generating tokens
* adds tests for default token generation #11 * adds Helpers class for static methods used in tests * adds commons-codec dependency for base64 encoding * Helpers uses a more concise HMAC-SHA1 signing routine than util.GenerateMac
1 parent cc9f434 commit 20a44ea

File tree

4 files changed

+104
-23
lines changed

4 files changed

+104
-23
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependencies {
3232
compile group: 'org.codehaus.jackson', name: 'jackson-mapper-asl', version: '1.9.13'
3333
compile group: 'org.codehaus.jackson', name: 'jackson-core-asl', version: '1.9.13'
3434
compile group: 'commons-validator', name: 'commons-validator', version: '1.4.0'
35+
compile group: 'commons-codec', name: 'commons-codec', version: '1.9'
3536
// TODO: find out how to initialize these dependencies properly, or remove them
3637
//compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.5'
3738
//compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.5'

src/main/java/com/opentok/api/OpenTok.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public String generateToken(String sessionId, String role, long expireTime, Stri
168168
throw new OpenTokSessionNotFoundException("Session not found");
169169
}
170170

171-
Session session = new Session(decodedSessionId, apiKey, apiSecret);
171+
Session session = new Session(sessionId, apiKey, apiSecret);
172172
return session.generateToken(role, expireTime, connectionData);
173173
}
174174

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.opentok.test;
2+
3+
import java.io.UnsupportedEncodingException;
4+
import java.security.InvalidKeyException;
5+
import java.security.NoSuchAlgorithmException;
6+
import java.security.SignatureException;
7+
import java.util.Formatter;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import org.apache.commons.codec.binary.Base64;
11+
12+
import javax.crypto.Mac;
13+
import javax.crypto.spec.SecretKeySpec;
14+
import java.net.URLDecoder;
15+
16+
/**
17+
* Created by ankur on 4/14/14.
18+
*/
19+
public class Helpers {
20+
21+
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
22+
23+
public static Map<String, String> decodeToken(String token) throws UnsupportedEncodingException {
24+
Map<String, String> tokenData = new HashMap<String, String>();
25+
token = token.substring(4);
26+
byte[] buffer = Base64.decodeBase64(token);
27+
String decoded = new String(buffer, "UTF-8");
28+
String[] decodedParts = decoded.split(":");
29+
for (String part : decodedParts) {
30+
tokenData.putAll(decodeFormData(part));
31+
}
32+
return tokenData;
33+
}
34+
35+
public static boolean verifyTokenSignature(String token, String apiSecret) throws
36+
UnsupportedEncodingException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
37+
token = token.substring(4);
38+
byte[] buffer = Base64.decodeBase64(token);
39+
String decoded = new String(buffer, "UTF-8");
40+
String[] decodedParts = decoded.split(":");
41+
String signature = decodeToken(token).get("sig");
42+
return (signature.equals(signData(decodedParts[1], apiSecret)));
43+
}
44+
45+
private static Map<String, String> decodeFormData(String formData) throws UnsupportedEncodingException {
46+
Map<String, String> decodedFormData = new HashMap<String, String>();
47+
String[] pairs = formData.split("\\&");
48+
for (int i = 0; i < pairs.length; i++) {
49+
String[] fields = pairs[i].split("=");
50+
String name = URLDecoder.decode(fields[0], "UTF-8");
51+
String value = URLDecoder.decode(fields[1], "UTF-8");
52+
decodedFormData.put(name, value);
53+
}
54+
return decodedFormData;
55+
}
56+
57+
// -- credit: https://gist.github.com/ishikawa/88599
58+
59+
private static String toHexString(byte[] bytes) {
60+
Formatter formatter = new Formatter();
61+
for (byte b : bytes) {
62+
formatter.format("%02x", b);
63+
}
64+
return formatter.toString();
65+
}
66+
67+
private static String signData(String data, String key)
68+
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException
69+
{
70+
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
71+
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
72+
mac.init(signingKey);
73+
return toHexString(mac.doFinal(data.getBytes()));
74+
}
75+
}

src/test/java/com/opentok/test/OpenTokTest.java

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@
66

77
package com.opentok.test;
88

9+
import java.io.UnsupportedEncodingException;
10+
import java.security.InvalidKeyException;
11+
import java.security.NoSuchAlgorithmException;
12+
import java.security.SignatureException;
913
import java.util.Date;
1014
import java.util.HashMap;
1115
import java.util.Map;
1216
import java.util.Map.Entry;
1317

18+
import com.opentok.test.Helpers;
19+
1420
import com.opentok.api.OpenTok;
1521
import com.opentok.api.Session;
1622
import com.opentok.api.constants.Version;
@@ -153,28 +159,27 @@ public void testCreateBadSession() throws OpenTokException {
153159

154160
// TODO: test session creation conditions that result in errors
155161

156-
// @Test
157-
// public void testRoleDefault() throws OpenTokException {
158-
// Session s= sdk.createSession();
159-
// String t = s.generateToken();
160-
// TokBoxXML xml = get_token_info(t);
161-
//
162-
// String expectedRole = "publisher";
163-
// String actualRole = xml.getElementValue("role", "token").trim();
164-
// Assert.assertEquals("Java SDK tests: role default not default (publisher)", expectedRole, actualRole);
165-
//
166-
// // Permissions are set as an empty node in the xml
167-
// // Verify that the expected permission node is there
168-
// // Verify nodes for permissions not granted to the role are not there
169-
// Assert.assertTrue("Java SDK tests: default role does not have subscriber permissions", xml.hasElement("subscribe", "permissions"));
170-
// Assert.assertTrue("Java SDK tests: default role does not have publisher permissions", xml.hasElement("publish", "permissions"));
171-
// Assert.assertTrue("Java SDK tests: default role does not have signal permissions", xml.hasElement("signal", "permissions"));
172-
// Assert.assertFalse("Java SDK tests: default role should not have forceunpublish permissions", xml.hasElement("forceunpublish", "permissions"));
173-
// Assert.assertFalse("Java SDK tests: default role should not have forcedisconnect permissions", xml.hasElement("forcedisconnect", "permissions"));
174-
// Assert.assertFalse("Java SDK tests: default role should not have record permissions", xml.hasElement("record", "permissions"));
175-
// Assert.assertFalse("Java SDK tests: default role should not have playback permissions", xml.hasElement("playback", "permissions"));
176-
// }
177-
//
162+
@Test
163+
public void testRoleDefault() throws
164+
OpenTokException, UnsupportedEncodingException, NoSuchAlgorithmException, SignatureException,
165+
InvalidKeyException {
166+
167+
int apiKey = 123456;
168+
String apiSecret = "1234567890abcdef1234567890abcdef1234567890";
169+
OpenTok opentok = new OpenTok(apiKey, apiSecret);
170+
String sessionId = "1_MX4xMjM0NTZ-flNhdCBNYXIgMTUgMTQ6NDI6MjMgUERUIDIwMTR-MC40OTAxMzAyNX4";
171+
172+
String token = opentok.generateToken(sessionId);
173+
174+
assertNotNull(token);
175+
assertTrue(Helpers.verifyTokenSignature(token, apiSecret));
176+
177+
Map<String, String> tokenData = Helpers.decodeToken(token);
178+
assertEquals(Integer.toString(apiKey), tokenData.get("partner_id"));
179+
assertNotNull(tokenData.get("create_time"));
180+
assertNotNull(tokenData.get("nonce"));
181+
}
182+
178183
// @Test
179184
// public void testRolePublisher() throws OpenTokException {
180185
// Session s= sdk.createSession();

0 commit comments

Comments
 (0)