1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.kathrynhuxtable.middleware.shibshim.filter;
19
20 import java.io.IOException;
21 import java.io.UnsupportedEncodingException;
22 import java.util.Date;
23
24 import javax.servlet.FilterChain;
25 import javax.servlet.ServletException;
26 import javax.servlet.ServletRequest;
27 import javax.servlet.ServletResponse;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
30 import javax.servlet.http.HttpSession;
31
32 import org.apache.log4j.Logger;
33 import org.kathrynhuxtable.middleware.shibshim.util.AESCrypt;
34 import org.kathrynhuxtable.middleware.shibshim.util.AESCryptException;
35 import org.kathrynhuxtable.middleware.shibshim.util.Base64;
36 import org.kathrynhuxtable.middleware.shibshim.util.RSASignature;
37 import org.kathrynhuxtable.middleware.shibshim.util.RSASignatureException;
38
39
40
41
42
43
44
45 public class FilterHandler {
46
47
48
49 private static Logger log = Logger.getLogger(FilterHandler.class.getName());
50
51
52
53
54 private static final int DEFAULT_SSL_PORT = 443;
55
56
57
58
59 private HttpServletRequest request = null;
60
61
62
63
64 private HttpServletResponse response = null;
65
66
67
68
69 private FilterChain chain = null;
70
71
72
73
74 private ShibShimFilter filter = null;
75
76
77
78
79 private HttpSession session = null;
80
81
82
83
84 private UserAttributesImpl attributes = null;
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 public FilterHandler(ServletRequest req, ServletResponse res, FilterChain chain, ShibShimFilter filter) throws ImmediateReturnException {
103
104 if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)) {
105 ErrorPage.displayErrorPage(res, "The Shibboleth Shim filter only supports HTTP and HTTPS");
106 }
107
108
109 request = (HttpServletRequest) req;
110 response = (HttpServletResponse) res;
111 log.debug("ServletPath = \"" + request.getServletPath() + "\"");
112
113 this.chain = chain;
114 this.filter = filter;
115
116
117
118 if (filter.getApplication() == null) {
119 String application = request.getServerName();
120 if (request.getServerPort() != DEFAULT_SSL_PORT) {
121 application += ":" + request.getServerPort();
122 }
123 application += request.getContextPath();
124 filter.setApplication(application);
125 }
126
127
128 session = request.getSession(false);
129 if (session != null) {
130 attributes = (UserAttributesImpl) session.getAttribute(filter.getSessionAttribute());
131 }
132 }
133
134
135
136
137
138
139
140
141
142
143
144
145 public void filterRequest() throws ImmediateReturnException {
146
147 handleLogin();
148 handleLogout();
149
150
151
152 if (session == null) {
153 session = request.getSession(true);
154 }
155
156
157 initializeAttributes(false);
158
159
160
161
162 handleACS();
163
164
165
166 attributes.put("config-application", filter.getApplication());
167
168
169
170 long currentTime = new Date().getTime();
171 long verifyTime = getIntConfigHeader("config-verifytime", 0);
172
173 log.debug("Do we need to get user info");
174 verifyUser(attributes.getHeader("handle"), currentTime, verifyTime);
175
176
177 if (!attributes.hasAttributes() && filter.isSessionRequired()) {
178 String url = request.getRequestURL().toString();
179 String queryString = request.getQueryString();
180 if (queryString != null) {
181 url += "?" + queryString;
182 }
183
184 redirectToLogin(url);
185 }
186
187
188 if (attributes.hasAttributes()) {
189
190
191 long oldCurrentTime = getIntConfigHeader("config-currenttime", currentTime);
192 attributes.put("config-lasttime", Long.toString(oldCurrentTime));
193 attributes.put("config-currenttime", Long.toString(currentTime));
194
195
196
197
198 }
199
200
201
202 if (checkForACSPath()) {
203 redirectToTarget();
204 } else {
205 wrapRequest();
206 }
207 }
208
209
210
211
212
213
214
215
216 private void redirectToTarget() throws ImmediateReturnException {
217 String url = request.getParameter("target");
218 if (url == null) {
219 url = "";
220 } else if (url.charAt(0) != '/') {
221 url = request.getContextPath() + "/" + url;
222 } else {
223 url = request.getContextPath() + url;
224 }
225 generalShibShimRedirect(url);
226 }
227
228
229
230
231
232
233
234
235 private void wrapRequest() throws ImmediateReturnException {
236 RequestWrapper requestWrapper = new RequestWrapper(request, attributes, filter.getRemoteUserAttribute());
237
238 try {
239 chain.doFilter(requestWrapper, response);
240 } catch (ServletException se) {
241 ErrorPage.displayErrorPage(response, "Error protecting application: " + se);
242 } catch (IOException ie) {
243 ErrorPage.displayErrorPage(response, "Error protecting application: " + ie);
244 }
245 }
246
247
248
249
250
251
252 private boolean checkForACSPath() {
253 return request.getServletPath().equals(filter.getLocalAcsPath());
254 }
255
256
257
258
259
260
261
262 private void handleACS() throws ImmediateReturnException {
263 if (!checkForACSPath()) {
264 return;
265 }
266
267 attributes.clearUserInfo();
268 log.debug("Trying to get user info");
269 try {
270 String userInfo = extractAndVerifyUserInfo();
271 attributes.parseUserInfo(userInfo);
272 attributes.put("config-attributes", userInfo);
273 } catch (UserInfoException e) {
274 ErrorPage.displayErrorPage(response, e.getMessage());
275 }
276 log.debug("Back from getting user info");
277 }
278
279
280
281
282
283
284 private boolean checkForLoginPath() {
285 return request.getServletPath().equals(filter.getLoginRedirectPath());
286 }
287
288
289
290
291
292
293
294
295
296 private void handleLogin() throws ImmediateReturnException {
297 if (!checkForLoginPath()) {
298 return;
299 }
300
301
302 if (session == null) {
303 session = request.getSession(true);
304 }
305 initializeAttributes(true);
306
307
308 String target = request.getParameter("target");
309 if (target == null) {
310 target = filter.getApplication();
311 } else {
312 try {
313 target = java.net.URLDecoder.decode(target, "US-ASCII");
314 } catch (IOException ie) {
315 log.error("IOException encountered decoding target: " + ie);
316 target = filter.getApplication();
317 }
318 }
319
320
321
322 String force = request.getParameter("force");
323 if (force != null && force.equalsIgnoreCase("true")) {
324 redirectToForceLogin(session.getId(), target);
325 } else {
326 redirectToLogin(target);
327 }
328 }
329
330
331
332
333
334
335 private boolean checkForLogoutPath() {
336 return request.getServletPath().equals(filter.getLogoutRedirectPath());
337 }
338
339
340
341
342
343
344
345
346
347 private void handleLogout() throws ImmediateReturnException {
348 if (!checkForLogoutPath()) {
349 return;
350 }
351
352
353 if (session != null) {
354 session.invalidate();
355 }
356
357
358 String target = request.getParameter("target");
359 if (target != null) {
360 try {
361 target = java.net.URLDecoder.decode(target, "US-ASCII");
362 } catch (IOException ie) {
363 log.error("IOException encountered decoding target: " + ie);
364 target = "";
365 }
366 }
367 redirectToLogout(target);
368 }
369
370
371
372
373
374
375
376
377
378
379
380 private boolean isTimeToVerifyAuthentication(long currentTime, long verifyTime) {
381
382
383
384 return false;
385 }
386
387
388
389
390
391
392
393
394
395
396
397
398
399 private void verifyUser(String handle, long currentTime, long verifyTime) throws ImmediateReturnException {
400 if (!isTimeToVerifyAuthentication(currentTime, verifyTime)) {
401 return;
402 }
403 }
404
405
406
407
408
409
410
411
412
413
414 protected void redirectToLogin(String url) throws ImmediateReturnException {
415 String acs = filter.getAcsUrl();
416 int start = 0;
417 if (url.startsWith("https://")) {
418 acs += "/https/";
419 start = "https://".length();
420 } else if (url.startsWith("http://")) {
421 acs += "/http/";
422 start = "http://".length();
423 }
424
425 int slash = url.indexOf('/', start);
426 if (slash < 0) {
427 slash = url.length();
428 }
429 acs += url.substring(start, slash) + request.getContextPath() + filter.getLocalAcsPath();
430
431 String target = url.replaceFirst("^https?://[^/]+" + request.getContextPath(), "");
432
433
434
435
436
437
438
439 log.debug("User info is still empty");
440 try {
441 String redirectUrl = acs + "?target=" + java.net.URLEncoder.encode(target, "US-ASCII");
442 generalShibShimRedirect(redirectUrl);
443 } catch (UnsupportedEncodingException e) {
444 ErrorPage.displayErrorPage(response, "Got error attempting to log in");
445 }
446 }
447
448
449
450
451
452
453
454
455
456
457
458 protected void redirectToForceLogin(String sessionId, String returnUrl) throws ImmediateReturnException {
459 try {
460 String url = filter.getAcsUrl() + "?" + "return=" + java.net.URLEncoder.encode(returnUrl, "US-ASCII");
461 generalShibShimRedirect(url);
462 } catch (UnsupportedEncodingException e) {
463 ErrorPage.displayErrorPage(response, "Got error attempting to log in");
464 }
465 }
466
467
468
469
470
471
472
473
474
475 protected void redirectToLogout(String returnUrl) throws ImmediateReturnException {
476 try {
477 String url = filter.getAcsUrl();
478 if (returnUrl != null) {
479 url += "?return=" + java.net.URLEncoder.encode(returnUrl, "US-ASCII");
480 }
481 generalShibShimRedirect(url);
482 } catch (UnsupportedEncodingException e) {
483 ErrorPage.displayErrorPage(response, "Got error attempting to log out");
484 }
485 }
486
487
488
489
490
491
492
493
494
495 protected void generalShibShimRedirect(String url) throws ImmediateReturnException {
496 try {
497 log.debug("Redirecting to \"" + url + "\"");
498 response.sendRedirect(url);
499 } catch (IOException e) {
500
501 }
502 throw new ImmediateReturnException();
503 }
504
505
506
507
508
509
510
511
512
513 protected String extractAndVerifyUserInfo() throws ImmediateReturnException {
514
515 byte[] clearText = null;
516 try {
517 String base64CipherText = request.getParameter("assertion");
518 byte[] cipherText = Base64.decode(base64CipherText);
519 clearText = AESCrypt.decrypt(cipherText, filter.getShibShimServerCryptKey());
520 } catch (AESCryptException e) {
521 ErrorPage.displayErrorPage(response, "AES decrypt exception: " + e);
522 }
523
524
525 boolean status = false;
526 try {
527 String signature = request.getParameter("signature");
528 byte[] sigBlock = Base64.decode(signature);
529 status = RSASignature.verify(clearText, sigBlock, filter.getShibShimServerCert());
530 } catch (RSASignatureException e) {
531 ErrorPage.displayErrorPage(response, "RSA signature exception: " + e);
532 }
533
534 if (!status) {
535 ErrorPage.displayErrorPage(response, "The Shibboleth Shim Server signature failed to verify");
536 }
537
538 String assertion = new String(clearText);
539 log.debug("assertion = \"" + assertion + "\"");
540 return assertion;
541 }
542
543
544
545
546
547
548
549
550
551
552
553
554
555 private long getIntConfigHeader(String attr, long defaultValue) throws ImmediateReturnException {
556 long intValue = defaultValue;
557 String tmp = (String) attributes.get(attr);
558 if (tmp != null) {
559 try {
560 intValue = Long.parseLong(tmp);
561 } catch (NumberFormatException e) {
562 ErrorPage.displayErrorPage(response, e.getMessage());
563 }
564 }
565 return intValue;
566 }
567
568
569
570
571
572
573
574
575
576
577 private void initializeAttributes(boolean clearAttributes) throws ImmediateReturnException {
578 if (attributes == null) {
579 attributes = new UserAttributesImpl(filter.getAttributeMap());
580 if (attributes == null) {
581 log.error("Cannot create " + filter.getSessionAttribute() + " object");
582 ErrorPage.displayErrorPage(response, "Unable to log in due to a system problem on the application's server");
583 }
584 session.setAttribute(filter.getSessionAttribute(), attributes);
585 }
586
587 if (clearAttributes) {
588 attributes.clearUserInfo();
589 }
590 }
591 }