55import javax .crypto .Mac ;
66import javax .crypto .spec .SecretKeySpec ;
77import javax .xml .bind .DatatypeConverter ;
8+ import java .io .UnsupportedEncodingException ;
9+ import java .net .URLEncoder ;
810import java .security .InvalidKeyException ;
911import java .security .NoSuchAlgorithmException ;
1012import java .util .ArrayList ;
13+ import java .util .Arrays ;
1114import java .util .Calendar ;
1215import java .util .TimeZone ;
16+ import java .util .regex .Matcher ;
17+ import java .util .regex .Pattern ;
1318
1419/**
1520 * Authentication Token generator
1621 */
1722public class AuthToken {
18- public String tokenName = Cloudinary .AKAMAI_TOKEN_NAME ;
23+ public static final AuthToken NULL_AUTH_TOKEN = new AuthToken ().setNull ();
24+ public static final String AUTH_TOKEN_NAME = "__cld_token__" ;
25+
26+ public String tokenName = AUTH_TOKEN_NAME ;
1927 public String key ;
2028 public long startTime ;
21- public long endTime ;
29+ public long expiration ;
2230 public String ip ;
2331 public String acl ;
24- public long window ;
32+ public long duration ;
33+ private boolean isNullToken = false ;
34+
35+ public AuthToken () {
36+ }
2537
2638 public AuthToken (String key ) {
2739 this .key = key ;
2840 }
2941
30- public AuthToken setTokenName (String tokenName ) {
42+ public AuthToken tokenName (String tokenName ) {
3143 this .tokenName = tokenName ;
3244 return this ;
3345 }
@@ -38,41 +50,41 @@ public AuthToken setTokenName(String tokenName) {
3850 * @param startTime in seconds since epoch
3951 * @return
4052 */
41- public AuthToken setStartTime (long startTime ) {
53+ public AuthToken startTime (long startTime ) {
4254 this .startTime = startTime ;
4355 return this ;
4456 }
4557
4658 /**
4759 * Set the end time (expiration) of the token
4860 *
49- * @param endTime in seconds since epoch
61+ * @param expiration in seconds since epoch
5062 * @return
5163 */
52- public AuthToken setEndTime (long endTime ) {
53- this .endTime = endTime ;
64+ public AuthToken expiration (long expiration ) {
65+ this .expiration = expiration ;
5466 return this ;
5567 }
5668
57- public AuthToken setIp (String ip ) {
69+ public AuthToken ip (String ip ) {
5870 this .ip = ip ;
5971 return this ;
6072 }
6173
62- public AuthToken setAcl (String acl ) {
74+ public AuthToken acl (String acl ) {
6375 this .acl = acl ;
6476 return this ;
6577 }
6678
6779 /**
6880 * The duration of the token in seconds. This value is used to calculate the expiration of the token.
69- * It is ignored if endTime is provided.
81+ * It is ignored if expiration is provided.
7082 *
71- * @param window
83+ * @param duration in seconds
7284 * @return
7385 */
74- public AuthToken setWindow (long window ) {
75- this .window = window ;
86+ public AuthToken duration (long duration ) {
87+ this .duration = duration ;
7688 return this ;
7789 }
7890
@@ -86,13 +98,13 @@ public String generate() {
8698 }
8799
88100 public String generate (String url ) {
89- long expiration = endTime ;
101+ long expiration = this . expiration ;
90102 if (expiration == 0 ) {
91- if (window > 0 ) {
103+ if (duration > 0 ) {
92104 final long start = startTime > 0 ? startTime : Calendar .getInstance (TimeZone .getTimeZone ("UTC" )).getTimeInMillis () / 1000L ;
93- expiration = start + window ;
105+ expiration = start + duration ;
94106 } else {
95- throw new IllegalArgumentException ("Must provide either endTime or window " );
107+ throw new IllegalArgumentException ("Must provide either expiration or duration " );
96108 }
97109 }
98110 ArrayList <String > tokenParts = new ArrayList <String >();
@@ -103,19 +115,56 @@ public String generate(String url) {
103115 tokenParts .add ("st=" + startTime );
104116 }
105117 tokenParts .add ("exp=" + expiration );
106- if (url != null ) {
107- tokenParts .add ("url=" + url );
108- } else if (acl != null ) {
118+ if (acl != null ) {
109119 tokenParts .add ("acl=" + acl );
110- } else {
111- throw new IllegalArgumentException ("Must provide either url or acl" );
112120 }
113- String auth = digest (StringUtils .join (tokenParts , "~" ));
121+ ArrayList <String > toSign = new ArrayList <String >(tokenParts );
122+ if (url != null ) {
123+
124+ try {
125+ toSign .add ("url=" + escapeUrl (url ));
126+ } catch (UnsupportedEncodingException e ) {
127+ e .printStackTrace ();
128+ }
129+ }
130+ String auth = digest (StringUtils .join (toSign , "~" ));
114131 tokenParts .add ("hmac=" + auth );
115132 return tokenName + "=" + StringUtils .join (tokenParts , "~" );
116133
117134 }
118135
136+ /**
137+ * Escape url using lowercase hex code
138+ * @param url a url string
139+ * @return escaped url
140+ * @throws UnsupportedEncodingException see {@link URLEncoder#encode}
141+ */
142+ private String escapeUrl (String url ) throws UnsupportedEncodingException {
143+ String escaped ;
144+ StringBuilder sb = new StringBuilder (URLEncoder .encode (url , "UTF-8" ));
145+ String regex = "%.." ;
146+ Pattern p = Pattern .compile (regex ); // Create the pattern.
147+ Matcher matcher = p .matcher (sb ); // Create the matcher.
148+ while (matcher .find ()) {
149+ String buf = sb .substring (matcher .start (), matcher .end ()).toLowerCase ();
150+ sb .replace (matcher .start (), matcher .end (), buf );
151+ }
152+ escaped = sb .toString ();
153+ return escaped ;
154+ }
155+
156+
157+ public AuthToken copy () {
158+ final AuthToken authToken = new AuthToken (key );
159+ authToken .tokenName = tokenName ;
160+ authToken .startTime = startTime ;
161+ authToken .expiration = expiration ;
162+ authToken .ip = ip ;
163+ authToken .acl = acl ;
164+ authToken .duration = duration ;
165+ return authToken ;
166+ }
167+
119168 private String digest (String message ) {
120169 byte [] binKey = DatatypeConverter .parseHexBinary (key );
121170 try {
@@ -132,5 +181,34 @@ private String digest(String message) {
132181 return null ;
133182 }
134183
184+ private AuthToken setNull () {
185+ isNullToken = true ;
186+ return this ;
187+ }
135188
189+ @ Override
190+ public boolean equals (Object o ) {
191+ if (o instanceof AuthToken ) {
192+ AuthToken other = (AuthToken ) o ;
193+ return (isNullToken && other .isNullToken ) ||
194+ key == null ? other .key == null : key .equals (other .key ) &&
195+ tokenName .equals (other .tokenName ) &&
196+ startTime == other .startTime &&
197+ expiration == other .expiration &&
198+ duration == other .duration &&
199+ (ip == null ? other .ip == null : ip .equals (other .ip )) &&
200+ (acl == null ? other .acl == null : acl .equals (other .acl ));
201+ } else {
202+ return false ;
203+ }
204+ }
205+
206+ @ Override
207+ public int hashCode () {
208+ if (isNullToken ) {
209+ return 0 ;
210+ } else {
211+ return Arrays .asList (tokenName , startTime , expiration , duration , ip , acl ).hashCode ();
212+ }
213+ }
136214}
0 commit comments