Skip to content

Commit f05d4a9

Browse files
author
Amir Tocker
committed
Add AuthToken(Map) constructor. Add AuthToken.merge(). Encode full URL path.
1 parent 87fa4db commit f05d4a9

File tree

5 files changed

+183
-153
lines changed

5 files changed

+183
-153
lines changed

cloudinary-core/src/main/java/com/cloudinary/AuthToken.java

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.cloudinary;
22

3+
import com.cloudinary.utils.ObjectUtils;
34
import com.cloudinary.utils.StringUtils;
45

56
import javax.crypto.Mac;
@@ -9,17 +10,17 @@
910
import java.net.URLEncoder;
1011
import java.security.InvalidKeyException;
1112
import java.security.NoSuchAlgorithmException;
12-
import java.util.ArrayList;
13-
import java.util.Arrays;
14-
import java.util.Calendar;
15-
import java.util.TimeZone;
13+
import java.util.*;
1614
import java.util.regex.Matcher;
1715
import java.util.regex.Pattern;
1816

1917
/**
2018
* Authentication Token generator
2119
*/
2220
public class AuthToken {
21+
/**
22+
* A null AuthToken, which can be passed to a method to override global settings.
23+
*/
2324
public static final AuthToken NULL_AUTH_TOKEN = new AuthToken().setNull();
2425
public static final String AUTH_TOKEN_NAME = "__cld_token__";
2526

@@ -39,6 +40,29 @@ public AuthToken(String key) {
3940
this.key = key;
4041
}
4142

43+
/**
44+
* Create a new AuthToken configuration.
45+
*
46+
* @param options The following keys may be used in the options: key, startTime, expiration, ip, acl, duration.
47+
*/
48+
public AuthToken(Map options) {
49+
if (options != null) {
50+
this.tokenName = ObjectUtils.asString( options.get("tokenName"), this.tokenName);
51+
this.key = (String) options.get("key");
52+
this.startTime = ObjectUtils.asLong(options.get("startTime"), 0L);
53+
this.expiration = ObjectUtils.asLong(options.get("expiration"),0L);
54+
this.ip = (String) options.get("ip");
55+
this.acl = (String) options.get("acl");
56+
this.duration = ObjectUtils.asLong(options.get("duration"), 0L);
57+
}
58+
59+
}
60+
61+
/**
62+
* Create a new AuthToken configuration overriding the default token name.
63+
* @param tokenName the name of the token. must be supported by the server.
64+
* @return this
65+
*/
4266
public AuthToken tokenName(String tokenName) {
4367
this.tokenName = tokenName;
4468
return this;
@@ -48,7 +72,7 @@ public AuthToken tokenName(String tokenName) {
4872
* Set the start time of the token. Defaults to now.
4973
*
5074
* @param startTime in seconds since epoch
51-
* @return
75+
* @return this
5276
*/
5377
public AuthToken startTime(long startTime) {
5478
this.startTime = startTime;
@@ -59,18 +83,28 @@ public AuthToken startTime(long startTime) {
5983
* Set the end time (expiration) of the token
6084
*
6185
* @param expiration in seconds since epoch
62-
* @return
86+
* @return this
6387
*/
6488
public AuthToken expiration(long expiration) {
6589
this.expiration = expiration;
6690
return this;
6791
}
6892

93+
/**
94+
* Set the ip of the client
95+
* @param ip
96+
* @return this
97+
*/
6998
public AuthToken ip(String ip) {
7099
this.ip = ip;
71100
return this;
72101
}
73102

103+
/**
104+
* Define an ACL for a cookie token
105+
* @param acl
106+
* @return this
107+
*/
74108
public AuthToken acl(String acl) {
75109
this.acl = acl;
76110
return this;
@@ -81,7 +115,7 @@ public AuthToken acl(String acl) {
81115
* It is ignored if expiration is provided.
82116
*
83117
* @param duration in seconds
84-
* @return
118+
* @return this
85119
*/
86120
public AuthToken duration(long duration) {
87121
this.duration = duration;
@@ -97,6 +131,11 @@ public String generate() {
97131
return generate(null);
98132
}
99133

134+
/**
135+
* Generate a URL token for the given URL.
136+
* @param url the URL to be authorized
137+
* @return a URL token
138+
*/
100139
public String generate(String url) {
101140
long expiration = this.expiration;
102141
if (expiration == 0) {
@@ -116,16 +155,11 @@ public String generate(String url) {
116155
}
117156
tokenParts.add("exp=" + expiration);
118157
if (acl != null) {
119-
tokenParts.add("acl=" + acl);
158+
tokenParts.add("acl=" + escapeToLower(acl));
120159
}
121160
ArrayList<String> toSign = new ArrayList<String>(tokenParts);
122161
if (url != null) {
123-
124-
try {
125-
toSign.add("url=" + escapeUrl(url));
126-
} catch (UnsupportedEncodingException e) {
127-
e.printStackTrace();
128-
}
162+
toSign.add("url=" + escapeToLower(url));
129163
}
130164
String auth = digest(StringUtils.join(toSign, "~"));
131165
tokenParts.add("hmac=" + auth);
@@ -137,11 +171,16 @@ public String generate(String url) {
137171
* Escape url using lowercase hex code
138172
* @param url a url string
139173
* @return escaped url
140-
* @throws UnsupportedEncodingException see {@link URLEncoder#encode}
141174
*/
142-
private String escapeUrl(String url) throws UnsupportedEncodingException {
175+
private String escapeToLower(String url) {
143176
String escaped;
144-
StringBuilder sb= new StringBuilder(URLEncoder.encode(url, "UTF-8"));
177+
String encodedUrl = null;
178+
try {
179+
encodedUrl = URLEncoder.encode(url, "UTF-8");
180+
} catch (UnsupportedEncodingException e) {
181+
throw new RuntimeException("Cannot escape string.", e);
182+
}
183+
StringBuilder sb= new StringBuilder(encodedUrl);
145184
String regex= "%..";
146185
Pattern p = Pattern.compile(regex); // Create the pattern.
147186
Matcher matcher = p.matcher(sb); // Create the matcher.
@@ -154,6 +193,10 @@ private String escapeUrl(String url) throws UnsupportedEncodingException {
154193
}
155194

156195

196+
/**
197+
* Create a copy of this AuthToken
198+
* @return a new AuthToken object
199+
*/
157200
public AuthToken copy() {
158201
final AuthToken authToken = new AuthToken(key);
159202
authToken.tokenName = tokenName;
@@ -165,6 +208,27 @@ public AuthToken copy() {
165208
return authToken;
166209
}
167210

211+
/**
212+
* Merge this token with another, creating a new token. Other's members who are not <code>null</code> or <code>0</code> will override this object's members.
213+
* @param other the token to merge from
214+
* @return a new token
215+
*/
216+
public AuthToken merge(AuthToken other) {
217+
if(other.equals(NULL_AUTH_TOKEN)) {
218+
// NULL_AUTH_TOKEN can't merge
219+
return other;
220+
}
221+
AuthToken merged = new AuthToken();
222+
merged.key = other.key != null ? other.key : this.key;
223+
merged.tokenName = other.tokenName != null ? other.tokenName : this.tokenName;
224+
merged.startTime = other.startTime != 0 ? other.startTime : this.startTime;
225+
merged.expiration = other.expiration != 0 ? other.expiration : this.expiration;
226+
merged.ip = other.ip != null ? other.ip : this.ip;
227+
merged.acl = other.acl != null ? other.acl : this.acl;
228+
merged.duration = other.duration != 0 ? other.duration : this.duration;
229+
return merged;
230+
}
231+
168232
private String digest(String message) {
169233
byte[] binKey = DatatypeConverter.parseHexBinary(key);
170234
try {
@@ -174,11 +238,10 @@ private String digest(String message) {
174238
final byte[] bytes = message.getBytes();
175239
return DatatypeConverter.printHexBinary(hmac.doFinal(bytes)).toLowerCase();
176240
} catch (NoSuchAlgorithmException e) {
177-
e.printStackTrace();
241+
throw new RuntimeException("Cannot create authorization token.", e);
178242
} catch (InvalidKeyException e) {
179-
e.printStackTrace();
243+
throw new RuntimeException("Cannot create authorization token.", e);
180244
}
181-
return null;
182245
}
183246

184247
private AuthToken setNull() {
@@ -191,7 +254,7 @@ public boolean equals(Object o) {
191254
if(o instanceof AuthToken) {
192255
AuthToken other = (AuthToken) o;
193256
return (isNullToken && other.isNullToken) ||
194-
key == null ? other.key == null : key.equals(other.key) &&
257+
(key == null ? other.key == null : key.equals(other.key)) &&
195258
tokenName.equals(other.tokenName) &&
196259
startTime == other.startTime &&
197260
expiration == other.expiration &&

cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ public Cloudinary(Map config) {
8989
}
9090

9191
public Cloudinary(String cloudinaryUrl) {
92-
this.config = new Configuration(parseConfigUrl(cloudinaryUrl));
92+
this.config = Configuration.from(cloudinaryUrl);
9393
loadStrategies();
9494
}
9595

9696
public Cloudinary() {
9797
String cloudinaryUrl = System.getProperty("CLOUDINARY_URL", System.getenv("CLOUDINARY_URL"));
9898
if (cloudinaryUrl != null) {
99-
this.config = new Configuration(parseConfigUrl(cloudinaryUrl));
99+
this.config = Configuration.from(cloudinaryUrl);
100100
} else {
101101
this.config = new Configuration();
102102
}
@@ -239,57 +239,6 @@ private String buildUrl(String base, Map<String, Object> params) throws Unsuppor
239239
return urlBuilder.toString();
240240
}
241241

242-
protected Map parseConfigUrl(String cloudinaryUrl) {
243-
Map params = new HashMap();
244-
URI cloudinaryUri = URI.create(cloudinaryUrl);
245-
params.put("cloud_name", cloudinaryUri.getHost());
246-
if (cloudinaryUri.getUserInfo() != null) {
247-
String[] creds = cloudinaryUri.getUserInfo().split(":");
248-
params.put("api_key", creds[0]);
249-
if (creds.length > 1) {
250-
params.put("api_secret", creds[1]);
251-
}
252-
}
253-
params.put("private_cdn", !StringUtils.isEmpty(cloudinaryUri.getPath()));
254-
params.put("secure_distribution", cloudinaryUri.getPath());
255-
if (cloudinaryUri.getQuery() != null) {
256-
for (String param : cloudinaryUri.getQuery().split("&")) {
257-
String[] keyValue = param.split("=");
258-
try {
259-
final String value = URLDecoder.decode(keyValue[1], "ASCII");
260-
final String key = keyValue[0];
261-
if(isNestedKey(key)) {
262-
putNestedValue(params, key, value);
263-
} else {
264-
params.put(key, value);
265-
}
266-
} catch (UnsupportedEncodingException e) {
267-
throw new RuntimeException("Unexpected exception", e);
268-
}
269-
}
270-
}
271-
return params;
272-
}
273-
274-
private void putNestedValue(Map params, String key, String value) {
275-
String[] chain = key.split("[\\[\\]]+");
276-
Map outer = params;
277-
String innerKey = chain[0];
278-
for (int i = 0; i < chain.length -1; i++, innerKey = chain[i]) {
279-
Map inner = (Map) outer.get(innerKey);
280-
if (inner == null) {
281-
inner = new HashMap();
282-
outer.put(innerKey, inner);
283-
}
284-
outer = inner;
285-
}
286-
outer.put(innerKey, value);
287-
}
288-
289-
private boolean isNestedKey(String key) {
290-
return key.matches("\\w+\\[\\w+\\]");
291-
}
292-
293242
byte[] getUTF8Bytes(String string) {
294243
try {
295244
return string.getBytes("UTF-8");

0 commit comments

Comments
 (0)