11package com .cloudinary ;
22
3+ import com .cloudinary .utils .ObjectUtils ;
34import com .cloudinary .utils .StringUtils ;
45
56import javax .crypto .Mac ;
910import java .net .URLEncoder ;
1011import java .security .InvalidKeyException ;
1112import 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 .*;
1614import java .util .regex .Matcher ;
1715import java .util .regex .Pattern ;
1816
1917/**
2018 * Authentication Token generator
2119 */
2220public 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 &&
0 commit comments