Skip to content

Commit 68a66a7

Browse files
committed
implements TokenOptions class with Builder pattern initialization, fixes #25
* refactors tests to use the new API * moves role validation into TokenOptions * makes expire_time a mandatory part of the token data
1 parent 6779191 commit 68a66a7

File tree

4 files changed

+165
-132
lines changed

4 files changed

+165
-132
lines changed

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

Lines changed: 35 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import javax.xml.bind.DatatypeConverter;
2020

21+
import com.opentok.api.constants.TokenOptions;
2122
import org.codehaus.jackson.JsonNode;
2223
import org.codehaus.jackson.map.ObjectMapper;
2324
import org.codehaus.jackson.type.TypeReference;
@@ -144,87 +145,39 @@ public OpenTok(int apiKey, String apiSecret, String apiUrl) {
144145
*
145146
* @return The token string.
146147
*/
147-
public String generateToken(String sessionId, String role, long expireTime, String connectionData) throws OpenTokException {
148+
public String generateToken(String sessionId, TokenOptions tokenOptions) throws OpenTokException {
148149

149150
if(sessionId == null || sessionId == "") {
150151
throw new OpenTokInvalidArgumentException("Session not valid");
151152
}
152-
String decodedSessionId = "";
153-
try {
154-
String subSessionId = sessionId.substring(2);
155-
for (int i = 0; i<3; i++){
156-
String newSessionId = subSessionId.concat(repeatString("=",i));
157-
decodedSessionId = new String(DatatypeConverter.parseBase64Binary(
158-
newSessionId.replace('-', '+').replace('_', '/')), "ISO8859_1");
159-
if (decodedSessionId.contains("~")){
160-
break;
161-
}
162-
}
163-
} catch (UnsupportedEncodingException e) {
164-
throw new OpenTokSessionNotFoundException("Session not found");
165-
}
166153

167-
if(!decodedSessionId.split("~")[1].equals(String.valueOf(apiKey))) {
168-
throw new OpenTokSessionNotFoundException("Session not found");
169-
}
154+
// TODO: use more succinct codec routines
155+
// String decodedSessionId = "";
156+
// try {
157+
// String subSessionId = sessionId.substring(2);
158+
// for (int i = 0; i<3; i++){
159+
// String newSessionId = subSessionId.concat(repeatString("=",i));
160+
// decodedSessionId = new String(DatatypeConverter.parseBase64Binary(
161+
// newSessionId.replace('-', '+').replace('_', '/')), "ISO8859_1");
162+
// if (decodedSessionId.contains("~")){
163+
// break;
164+
// }
165+
// }
166+
// } catch (UnsupportedEncodingException e) {
167+
// throw new OpenTokSessionNotFoundException("Session not found");
168+
// }
169+
//
170+
// if(!decodedSessionId.split("~")[1].equals(String.valueOf(apiKey))) {
171+
// throw new OpenTokSessionNotFoundException("Session not found");
172+
// }
170173

171174
Session session = new Session(sessionId, apiKey, apiSecret);
172-
return session.generateToken(role, expireTime, connectionData);
175+
return session.generateToken(tokenOptions);
173176
}
174177

175-
176-
177-
/**
178-
* Generates the token for the given session. The role is set to publisher, the token expires in
179-
* 24 hours, and there is no connection data.
180-
*
181-
* @param sessionId The session ID.
182-
*
183-
* @see #generateToken(String sessionId, String role, long expireTime, String connectionData)
184-
*/
185178
public String generateToken(String sessionId) throws OpenTokException {
186-
return this.generateToken(sessionId, RoleConstants.PUBLISHER, 0, null);
187-
}
188-
189-
190-
/**
191-
* Generates the token for the given session and with the specified role. The token expires in
192-
* 24 hours, and there is no connection data.
193-
*
194-
* @param sessionId The session ID.
195-
* @param role The role assigned to the token.
196-
*
197-
* @see #generateToken(String sessionId, String role, long expireTime, String connectionData)
198-
*/
199-
public String generateToken(String sessionId, String role) throws OpenTokException {
200-
return this.generateToken(sessionId, role, 0, null);
201-
}
202-
203-
/**
204-
* Generates the token for the given session, with the specified role and expiration time.
205-
* There is no connection data.
206-
*
207-
* @param sessionId The session ID.
208-
* @param role The role assigned to the token.
209-
* @param expireTime The expiration time, in seconds, since the UNIX epoch. Pass in 0 to use
210-
* the default expiration time of 24 hours after the token creation time. The maximum expiration
211-
* time is 30 days after the creation time.
212-
*
213-
* @see #generateToken(String sessionId, String role, long expireTime, String connectionData)
214-
*/
215-
public String generateToken(String sessionId, String role, Long expireTime) throws OpenTokException {
216-
return this.generateToken(sessionId, role, expireTime, null);
217-
}
218-
219-
/**
220-
* Creates an OpenTok session and returns the session ID, with the default properties. The
221-
* session uses the OpenTok media server. And the session uses the first client connecting
222-
* to determine the location of OpenTok server to use.
223-
*
224-
* @see #createSession(SessionProperties)
225-
*/
226-
public Session createSession() throws OpenTokException {
227-
return createSession(null);
179+
// NOTE: should there be a static defaultTokenOptions?
180+
return generateToken(sessionId, new TokenOptions.Builder().build());
228181
}
229182

230183
/**
@@ -337,6 +290,17 @@ public Session createSession(SessionProperties properties) throws OpenTokExcepti
337290
}
338291
}
339292

293+
/**
294+
* Creates an OpenTok session and returns the session ID, with the default properties. The
295+
* session uses the OpenTok media server. And the session uses the first client connecting
296+
* to determine the location of OpenTok server to use.
297+
*
298+
* @see #createSession(SessionProperties)
299+
*/
300+
public Session createSession() throws OpenTokException {
301+
return createSession(null);
302+
}
303+
340304
private static String repeatString(String str, int times){
341305
StringBuilder ret = new StringBuilder();
342306
for(int i = 0;i < times;i++) ret.append(str);

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

Lines changed: 36 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import java.net.URLEncoder;
55
import java.util.Random;
66

7-
import com.opentok.api.constants.RoleConstants;
87
import com.opentok.api.constants.SessionProperties;
8+
import com.opentok.api.constants.TokenOptions;
99
import com.opentok.exception.OpenTokException;
1010
import com.opentok.exception.OpenTokInvalidArgumentException;
1111
import com.opentok.exception.OpenTokRequestException;
@@ -54,35 +54,8 @@ public SessionProperties getProperties() {
5454
* @see #generateToken(String sessionId, String role, long expireTime, String connectionData)
5555
*/
5656
public String generateToken() throws OpenTokException {
57-
return this.generateToken(RoleConstants.PUBLISHER, 0, null);
58-
}
59-
60-
61-
/**
62-
* Generates the token for the given session and with the specified role. The token expires in
63-
* 24 hours, and there is no connection data.
64-
*
65-
* @param role The role assigned to the token.
66-
*
67-
* @see #generateToken(String sessionId, String role, long expireTime, String connectionData)
68-
*/
69-
public String generateToken(String role) throws OpenTokException {
70-
return this.generateToken(role, 0, null);
71-
}
72-
73-
/**
74-
* Generates the token for the given session, with the specified role and expiration time.
75-
* There is no connection data.
76-
*
77-
* @param role The role assigned to the token.
78-
* @param expireTime The expiration time, in seconds, since the UNIX epoch. Pass in 0 to use
79-
* the default expiration time of 24 hours after the token creation time. The maximum expiration
80-
* time is 30 days after the creation time.
81-
*
82-
* @see #generateToken(String sessionId, String role, long expireTime, String connectionData)
83-
*/
84-
public String generateToken(String role, Long expireTime) throws OpenTokException {
85-
return this.generateToken(role, expireTime, null);
57+
// NOTE: maybe there should be a static object for the defaultTokenOptions?
58+
return this.generateToken(new TokenOptions.Builder().build());
8659
}
8760

8861
/**
@@ -111,9 +84,17 @@ public String generateToken(String role, Long expireTime) throws OpenTokExceptio
11184
*
11285
* @return The token string.
11386
*/
114-
public String generateToken(String role, long expireTime,
115-
String connectionData) throws OpenTokInvalidArgumentException,
116-
OpenTokRequestException {
87+
public String generateToken(TokenOptions tokenOptions) throws
88+
OpenTokInvalidArgumentException, OpenTokRequestException {
89+
90+
if (tokenOptions == null) {
91+
throw new OpenTokInvalidArgumentException("Token options cannot be null");
92+
}
93+
94+
String role = tokenOptions.getRole();
95+
double expireTime = tokenOptions.getExpireTime(); // will be 0 if nothing was explicitly set
96+
String data = tokenOptions.getData(); // will be null if nothing was explicitly set
97+
11798
Long create_time = new Long(System.currentTimeMillis() / 1000).longValue();
11899
StringBuilder dataStringBuilder = new StringBuilder();
119100
//Build the string
@@ -128,30 +109,31 @@ public String generateToken(String role, long expireTime,
128109
dataStringBuilder.append("&role=");
129110
dataStringBuilder.append(role);
130111

131-
if(!RoleConstants.SUBSCRIBER.equals(role) &&
132-
!RoleConstants.PUBLISHER.equals(role) &&
133-
!RoleConstants.MODERATOR.equals(role) &&
134-
!"".equals(role))
135-
throw new OpenTokInvalidArgumentException(role + " is not a recognized role");
136-
137-
if (expireTime != 0) {
138-
if(expireTime < (System.currentTimeMillis() / 1000)-1)
139-
throw new OpenTokInvalidArgumentException("Expire time must be in the future");
140-
if(expireTime > (System.currentTimeMillis() / 1000 + 2592000))
141-
throw new OpenTokInvalidArgumentException("Expire time must be in the next 30 days");
142-
dataStringBuilder.append("&expire_time=");
143-
dataStringBuilder.append(expireTime);
112+
double now = System.currentTimeMillis() / 1000L;
113+
if (expireTime == 0) {
114+
expireTime = now + (60*60*24); // 1 day
115+
} else if(expireTime < now-1) {
116+
throw new OpenTokInvalidArgumentException(
117+
"Expire time must be in the future. relative time: "+ (expireTime - now));
118+
} else if(expireTime > (now + (60*60*24*30) /* 30 days */)) {
119+
throw new OpenTokInvalidArgumentException(
120+
"Expire time must be in the next 30 days. too large by "+ (expireTime - (now + (60*60*24*30))));
144121
}
145-
146-
if (connectionData != null) {
147-
if(connectionData.length() > 1000)
148-
throw new OpenTokInvalidArgumentException("Connection data must be less than 1000 characters");
122+
dataStringBuilder.append("&expire_time=");
123+
dataStringBuilder.append(expireTime);
124+
125+
if (data != null) {
126+
if(data.length() > 1000) {
127+
throw new OpenTokInvalidArgumentException(
128+
"Connection data must be less than 1000 characters. length: " + data.length());
129+
}
149130
dataStringBuilder.append("&connection_data=");
150131
try {
151-
dataStringBuilder.append(URLEncoder.encode(connectionData, "UTF-8"));
132+
dataStringBuilder.append(URLEncoder.encode(data, "UTF-8"));
152133
} catch (UnsupportedEncodingException e) {
153-
throw new OpenTokInvalidArgumentException("Error during URL encode of your connectionData: " + e.getMessage());
154-
};
134+
throw new OpenTokInvalidArgumentException(
135+
"Error during URL encode of your connection data: " + e.getMessage());
136+
}
155137
}
156138

157139

@@ -165,6 +147,7 @@ public String generateToken(String role, long expireTime,
165147

166148
innerBuilder.append("&sig=");
167149

150+
// TODO: user a more concise crypto routine
168151
innerBuilder.append(GenerateMac.calculateRFC2104HMAC(dataStringBuilder.toString(),
169152
this.apiSecret));
170153
innerBuilder.append(":");
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.opentok.api.constants;
2+
3+
import com.opentok.exception.OpenTokInvalidArgumentException;
4+
5+
/**
6+
* Created by ankur on 4/14/14.
7+
*/
8+
public class TokenOptions {
9+
10+
private String role;
11+
private double expireTime;
12+
private String data;
13+
14+
private TokenOptions(Builder builder) {
15+
this.role = builder.role != null ? builder.role : "publisher";
16+
17+
// default value calculated at token generation time
18+
this.expireTime = builder.expireTime;
19+
20+
// default value of null means to omit the key "connection_data" from the token
21+
this.data = builder.data;
22+
}
23+
24+
public String getRole() {
25+
return role;
26+
}
27+
28+
public double getExpireTime() {
29+
return expireTime;
30+
}
31+
32+
public String getData() {
33+
return data;
34+
}
35+
36+
// TODO: toMap() method?
37+
38+
public static class Builder {
39+
private String role;
40+
private double expireTime = 0;
41+
private String data;
42+
43+
public Builder role(String role) throws OpenTokInvalidArgumentException {
44+
if (role.equals("publisher") || role.equals("subscriber") || role.equals("moderator")) {
45+
this.role = role;
46+
} else {
47+
throw new OpenTokInvalidArgumentException("The given role is not valid: " + role);
48+
}
49+
return this;
50+
}
51+
52+
public Builder expireTime(double expireTime) {
53+
// NOTE: since this object can be stored/cached, validation should occur at token generation time
54+
this.expireTime = expireTime;
55+
return this;
56+
}
57+
58+
public Builder data(String data) throws OpenTokInvalidArgumentException {
59+
if (data.length() <= 1000) {
60+
this.data = data;
61+
} else {
62+
throw new OpenTokInvalidArgumentException(
63+
"The given connection data is too long, limit is 1000 characters: "+data.length());
64+
}
65+
return this;
66+
}
67+
68+
public TokenOptions build() {
69+
return new TokenOptions(this);
70+
}
71+
}
72+
73+
}

0 commit comments

Comments
 (0)