1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts.util;
19
20 import org.apache.struts.Globals;
21
22 import javax.servlet.http.HttpServletRequest;
23 import javax.servlet.http.HttpSession;
24
25 import java.security.MessageDigest;
26 import java.security.NoSuchAlgorithmException;
27
28 /***
29 * TokenProcessor is responsible for handling all token related functionality.
30 * The methods in this class are synchronized to protect token processing from
31 * multiple threads. Servlet containers are allowed to return a different
32 * HttpSession object for two threads accessing the same session so it is not
33 * possible to synchronize on the session.
34 *
35 * @since Struts 1.1
36 */
37 public class TokenProcessor {
38 /***
39 * The singleton instance of this class.
40 */
41 private static TokenProcessor instance = new TokenProcessor();
42
43 /***
44 * The timestamp used most recently to generate a token value.
45 */
46 private long previous;
47
48 /***
49 * Protected constructor for TokenProcessor. Use TokenProcessor.getInstance()
50 * to obtain a reference to the processor.
51 */
52 protected TokenProcessor() {
53 super();
54 }
55
56 /***
57 * Retrieves the singleton instance of this class.
58 */
59 public static TokenProcessor getInstance() {
60 return instance;
61 }
62
63 /***
64 * <p>Return <code>true</code> if there is a transaction token stored in
65 * the user's current session, and the value submitted as a request
66 * parameter with this action matches it. Returns <code>false</code>
67 * under any of the following circumstances:</p>
68 *
69 * <ul>
70 *
71 * <li>No session associated with this request</li>
72 *
73 * <li>No transaction token saved in the session</li>
74 *
75 * <li>No transaction token included as a request parameter</li>
76 *
77 * <li>The included transaction token value does not match the transaction
78 * token in the user's session</li>
79 *
80 * </ul>
81 *
82 * @param request The servlet request we are processing
83 */
84 public synchronized boolean isTokenValid(HttpServletRequest request) {
85 return this.isTokenValid(request, false);
86 }
87
88 /***
89 * Return <code>true</code> if there is a transaction token stored in the
90 * user's current session, and the value submitted as a request parameter
91 * with this action matches it. Returns <code>false</code>
92 *
93 * <ul>
94 *
95 * <li>No session associated with this request</li> <li>No transaction
96 * token saved in the session</li>
97 *
98 * <li>No transaction token included as a request parameter</li>
99 *
100 * <li>The included transaction token value does not match the transaction
101 * token in the user's session</li>
102 *
103 * </ul>
104 *
105 * @param request The servlet request we are processing
106 * @param reset Should we reset the token after checking it?
107 */
108 public synchronized boolean isTokenValid(HttpServletRequest request,
109 boolean reset) {
110
111 HttpSession session = request.getSession(false);
112
113 if (session == null) {
114 return false;
115 }
116
117
118
119 String saved =
120 (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
121
122 if (saved == null) {
123 return false;
124 }
125
126 if (reset) {
127 this.resetToken(request);
128 }
129
130
131 String token = request.getParameter(Globals.TOKEN_KEY);
132
133 if (token == null) {
134 return false;
135 }
136
137 return saved.equals(token);
138 }
139
140 /***
141 * Reset the saved transaction token in the user's session. This
142 * indicates that transactional token checking will not be needed on the
143 * next request that is submitted.
144 *
145 * @param request The servlet request we are processing
146 */
147 public synchronized void resetToken(HttpServletRequest request) {
148 HttpSession session = request.getSession(false);
149
150 if (session == null) {
151 return;
152 }
153
154 session.removeAttribute(Globals.TRANSACTION_TOKEN_KEY);
155 }
156
157 /***
158 * Save a new transaction token in the user's current session, creating a
159 * new session if necessary.
160 *
161 * @param request The servlet request we are processing
162 */
163 public synchronized void saveToken(HttpServletRequest request) {
164 HttpSession session = request.getSession();
165 String token = generateToken(request);
166
167 if (token != null) {
168 session.setAttribute(Globals.TRANSACTION_TOKEN_KEY, token);
169 }
170 }
171
172 /***
173 * Generate a new transaction token, to be used for enforcing a single
174 * request for a particular transaction.
175 *
176 * @param request The request we are processing
177 */
178 public synchronized String generateToken(HttpServletRequest request) {
179 HttpSession session = request.getSession();
180
181 return generateToken(session.getId());
182 }
183
184 /***
185 * Generate a new transaction token, to be used for enforcing a single
186 * request for a particular transaction.
187 *
188 * @param id a unique Identifier for the session or other context in which
189 * this token is to be used.
190 */
191 public synchronized String generateToken(String id) {
192 try {
193 long current = System.currentTimeMillis();
194
195 if (current == previous) {
196 current++;
197 }
198
199 previous = current;
200
201 byte[] now = new Long(current).toString().getBytes();
202 MessageDigest md = MessageDigest.getInstance("MD5");
203
204 md.update(id.getBytes());
205 md.update(now);
206
207 return toHex(md.digest());
208 } catch (NoSuchAlgorithmException e) {
209 return null;
210 }
211 }
212
213 /***
214 * Convert a byte array to a String of hexadecimal digits and return it.
215 *
216 * @param buffer The byte array to be converted
217 */
218 private String toHex(byte[] buffer) {
219 StringBuffer sb = new StringBuffer(buffer.length * 2);
220
221 for (int i = 0; i < buffer.length; i++) {
222 sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
223 sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
224 }
225
226 return sb.toString();
227 }
228 }