001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.core.web.filter;
017:
018: import java.io.BufferedReader;
019: import java.io.IOException;
020: import java.io.InputStreamReader;
021: import java.net.URL;
022: import java.util.Enumeration;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027:
028: import javax.servlet.Filter;
029: import javax.servlet.FilterChain;
030: import javax.servlet.FilterConfig;
031: import javax.servlet.ServletException;
032: import javax.servlet.ServletRequest;
033: import javax.servlet.ServletResponse;
034: import javax.servlet.http.HttpServletRequest;
035: import javax.servlet.http.HttpServletResponse;
036: import javax.servlet.http.HttpSession;
037:
038: import org.apache.commons.fileupload.DiskFileUpload;
039: import org.apache.commons.fileupload.FileItem;
040: import org.apache.commons.fileupload.FileUpload;
041: import org.apache.commons.fileupload.FileUploadException;
042: import org.apache.commons.lang.StringUtils;
043: import org.apache.log4j.Logger;
044:
045: /**
046: * This class is to do cas filtering for the kuali application
047: *
048: *
049: */
050: public class KualiCasFilter implements Filter {
051:
052: private static Logger LOG = Logger.getLogger(KualiCasFilter.class);
053:
054: private static final String USERNAME_HASH = "org.kuali.web.filter.UsernameHash";
055:
056: private static final String PRE_CAS_REDIRECT_PARAMS = "org.kuali.request.cas-parameters";
057:
058: private static final String CAS_SERVICE = "KUALI";
059:
060: private String validationURL;
061:
062: private String loginURL;
063:
064: /**
065: * during init we set the ssl handler, validation url, and login url
066: *
067: * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
068: */
069: public void init(FilterConfig filterConfig) throws ServletException {
070: System.setProperty("java.protocol.handler.pkgs",
071: "com.sun.net.ssl.internal.www.protocol");
072: validationURL = filterConfig
073: .getInitParameter("edu.yale.its.tp.cas.client.filter.validateUrl");
074: loginURL = filterConfig
075: .getInitParameter("edu.yale.its.tp.cas.client.filter.loginUrl");
076: }
077:
078: /**
079: * In this method, we check if the user has been authenticated already and if they have then we let the request through,
080: * otherwise we check for a cas ticket as a request parameter, if it is not there then we redirect to cas for login. If it is
081: * there then we attempt to validate the ticket. If the ticket is valid then we redirect them back to thier originating request,
082: * otherwise we redirect them back to CAS again.
083: *
084: * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
085: */
086: public void doFilter(ServletRequest request,
087: ServletResponse response, FilterChain chain)
088: throws IOException, ServletException {
089:
090: HttpServletRequest hrequest = (HttpServletRequest) request;
091: HttpServletResponse hresponse = (HttpServletResponse) response;
092: HttpSession session = hrequest.getSession();
093:
094: HashMap userList = (HashMap) session
095: .getAttribute(USERNAME_HASH);
096:
097: if (userList == null) {
098: userList = new HashMap();
099: session.setAttribute(USERNAME_HASH, userList);
100: }
101:
102: if (userList.get(CAS_SERVICE) != null) {
103: chain.doFilter(request, response);
104: } else {
105: String ticket = hrequest.getParameter("ticket");
106: if (ticket == null) {
107: saveRequestState(hrequest, getRequestStateMap(hrequest));
108: redirectToCas(hrequest, hresponse);
109: } else {
110: String username = null;
111: try {
112: username = validate(hrequest, ticket);
113: } catch (IOException ex) {
114: username = null;
115: }
116: if (username == null) {
117: saveRequestState(hrequest,
118: getRequestStateMap(hrequest));
119: redirectToCas(hrequest, hresponse);
120: } else {
121: userList.put(CAS_SERVICE, username);
122: Map savedRequestState = getSavedRequestState(hrequest);
123: clearSavedRequestState(hrequest);
124: redirectBackToOriginalRequest(hrequest,
125: savedRequestState, hresponse);
126: }
127: }
128: }
129: }
130:
131: /**
132: * Redirects browser to CAS
133: *
134: * @param hrequest
135: * @param hresponse
136: * @throws IOException
137: */
138: private void redirectToCas(HttpServletRequest hrequest,
139: HttpServletResponse hresponse) throws java.io.IOException {
140: LOG.info("redirecting to cas: " + loginURL + "?service="
141: + hrequest.getRequestURL().toString());
142: hresponse.sendRedirect(hresponse.encodeRedirectURL(loginURL
143: + "?service=" + hrequest.getRequestURL().toString()));
144: }
145:
146: /**
147: * This method uses a form to reinstate the orignal request, with noscript tags for users who do not use javascript, and with an
148: * automatic post for those who do have javascript enabled.
149: *
150: * @param hrequest
151: * @param hresponse
152: * @throws java.io.IOException
153: */
154: private void redirectBackToOriginalRequest(
155: HttpServletRequest hrequest,
156: Map originalRequestParameterMap,
157: HttpServletResponse hresponse) throws java.io.IOException {
158: LOG.info("redirecting back to original request: "
159: + hrequest.getRequestURL().toString());
160: hresponse.setContentType("text/html");
161: hresponse.setStatus(HttpServletResponse.SC_OK);
162: hresponse
163: .getOutputStream()
164: .println(
165: "<html><head><title>Session Timeout Recovery Page</title></head><body onload=\"document.forms[0].submit()\"><form method=\"POST\" action=\""
166: + hrequest.getRequestURL().toString()
167: + "\" >"
168: + generateHiddenInputFields(originalRequestParameterMap)
169: + "<noscript>Session Expired Click Here to Resume<input type=\"SUBMIT\" value=\"RESUME\"></noscript></form></body></html>");
170: hresponse.flushBuffer();
171: }
172:
173: /**
174: * This method saves of the request state in a known variable
175: *
176: * @param hrequest
177: * @param parameters
178: */
179: private void saveRequestState(HttpServletRequest hrequest,
180: Map parameters) {
181: hrequest.getSession().setAttribute(PRE_CAS_REDIRECT_PARAMS,
182: parameters);
183: }
184:
185: /**
186: * This method gets the saved request state from a known variable
187: *
188: * @param hrequest
189: * @param parameters
190: */
191: private Map getSavedRequestState(HttpServletRequest hrequest) {
192: return (Map) hrequest.getSession().getAttribute(
193: PRE_CAS_REDIRECT_PARAMS);
194: }
195:
196: /**
197: * This method clears the saved request state from the known variable
198: *
199: * @param hrequest
200: */
201: private void clearSavedRequestState(HttpServletRequest hrequest) {
202: hrequest.getSession().removeAttribute(PRE_CAS_REDIRECT_PARAMS);
203: }
204:
205: /**
206: * This method will look at the request and generate a map of all of the parameters
207: *
208: * @param hrequest
209: * @return
210: */
211: private Map getRequestStateMap(HttpServletRequest hrequest) {
212: Map parameters = null;
213: try {
214: if (FileUpload.isMultipartContent(hrequest)) {
215: DiskFileUpload upload = new DiskFileUpload();
216: List items = upload.parseRequest(hrequest);
217: parameters = new HashMap();
218: Iterator iter = items.iterator();
219: while (iter.hasNext()) {
220: FileItem item = (FileItem) iter.next();
221: if (item.isFormField()) {
222: String name = item.getFieldName();
223: String value = item.getString();
224: if (parameters.containsKey(name)) {
225: // then we add to the string array
226: String[] newValues = new String[((String[]) parameters
227: .get(name)).length];
228: for (int i = 0; i < ((String[]) parameters
229: .get(name)).length; i++) {
230: newValues[i] = ((String[]) parameters
231: .get(name))[i];
232: }
233: newValues[newValues.length - 1] = value;
234: parameters.put(name, newValues);
235: } else {
236: parameters
237: .put(name, new String[] { value });
238: }
239: } else {
240: // TODO drop these for now -- may want to display a message to the user that there
241: // file was not uploaded due to thier timeout, if we get into this set of code
242: }
243: }
244: if (parameters.containsKey("ticket")) {
245: parameters.remove("ticket");
246: }
247: } else {
248: parameters = new HashMap();
249: if (hrequest.getQueryString() != null
250: && hrequest.getQueryString().indexOf(
251: "channelUrl") >= 0) {
252: parameters
253: .put(
254: "channelUrl",
255: new String[] { hrequest
256: .getQueryString()
257: .substring(
258: hrequest
259: .getQueryString()
260: .indexOf(
261: "channelUrl") + 11,
262: hrequest
263: .getQueryString()
264: .length()) });
265: parameters.put("channelTitle",
266: new String[] { hrequest
267: .getParameter("channelTitle") });
268: } else {
269: String parameterName;
270: String[] parameterVals;
271: Enumeration parameterEnum = hrequest
272: .getParameterNames();
273: while (parameterEnum.hasMoreElements()) {
274: parameterName = (String) parameterEnum
275: .nextElement();
276: parameterVals = hrequest
277: .getParameterValues(parameterName);
278: if (!parameterName.equals("ticket")) {
279: parameters
280: .put(parameterName, parameterVals);
281: }
282: }
283: }
284: }
285: } catch (FileUploadException e) {
286: LOG
287: .error(
288: "Error caught while getting parameters to save off before sending to CAS",
289: e);
290: }
291: return parameters;
292: }
293:
294: /**
295: * This method will generate a query string rep for a map of String Arrays
296: *
297: * @param requestParameterMap
298: * @return
299: */
300: private String generateHiddenInputFields(Map requestParameterMap) {
301: StringBuffer hiddenInputFieldString = new StringBuffer();
302: for (Iterator iter = requestParameterMap.entrySet().iterator(); iter
303: .hasNext();) {
304: Map.Entry element = (Map.Entry) iter.next();
305: if (element.getValue() != null) {
306: String[] values = (String[]) element.getValue();
307: for (int i = 0; i < values.length; i++) {
308: hiddenInputFieldString
309: .append("<input type=\"hidden\" name=\""
310: + element.getKey() + "\" value=\""
311: + values[i] + "\">");
312: }
313: }
314: }
315: return hiddenInputFieldString.toString();
316: }
317:
318: /**
319: * This method will validate a cas ticket
320: *
321: * @param hrequest
322: * @param ticket
323: * @return
324: * @throws java.io.IOException
325: */
326: private String validate(HttpServletRequest hrequest, String ticket)
327: throws java.io.IOException {
328: URL url = new URL(validationURL + "?ticket=" + ticket
329: + "&service=" + hrequest.getRequestURL().toString());
330: return StringUtils.substringBetween(
331: getFullTextResponseForUrlRequest(url), "<cas:user>",
332: "</cas:user>");
333: }
334:
335: /**
336: * This method reads the response from a url request, and builds up a string representation of it then returns it to the caller
337: *
338: * @param url
339: * @return
340: * @throws IOException
341: */
342: private String getFullTextResponseForUrlRequest(URL url)
343: throws IOException {
344: BufferedReader in = new BufferedReader(new InputStreamReader(
345: url.openStream()));
346: String textLine = "";
347: String fullText = "";
348: while ((textLine = in.readLine()) != null) {
349: fullText += textLine;
350: }
351: try {
352: in.close();
353: } catch (IOException e) {
354: LOG.error("caught exception closing response URL: "
355: + e.getMessage());
356: }
357: return fullText;
358: }
359:
360: /**
361: * This method will return the network id used to authenticate to cas
362: *
363: * @param request
364: * @return
365: */
366: public static String getRemoteUser(HttpServletRequest request) {
367: HashMap userList = (HashMap) request.getSession().getAttribute(
368: USERNAME_HASH);
369: if (userList == null) {
370: return null;
371: }
372: LOG.info("getRemoteUser returning: "
373: + (String) userList.get(CAS_SERVICE));
374: return (String) userList.get(CAS_SERVICE);
375: }
376:
377: /**
378: * @see javax.servlet.Filter#destroy()
379: */
380: public void destroy() {
381: LOG.info("Shutting down cas filter");
382: }
383:
384: }
|