001/** 002 * Copyright 2005-2018 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.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/ecl2.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 */ 016package org.kuali.rice.krad.web.filter; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.CoreApiServiceLocator; 020import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 021import org.kuali.rice.krad.uif.UifConstants; 022import org.kuali.rice.krad.uif.UifParameters; 023import org.kuali.rice.krad.uif.service.ViewDictionaryService; 024import org.kuali.rice.krad.uif.service.ViewService; 025import org.kuali.rice.krad.uif.view.ViewSessionPolicy; 026import org.kuali.rice.krad.util.KRADConstants; 027import org.kuali.rice.krad.util.KRADUtils; 028import org.kuali.rice.krad.web.form.UifFormManager; 029import org.springframework.web.bind.annotation.RequestMethod; 030 031import javax.servlet.Filter; 032import javax.servlet.FilterChain; 033import javax.servlet.FilterConfig; 034import javax.servlet.ServletException; 035import javax.servlet.ServletRequest; 036import javax.servlet.ServletResponse; 037import javax.servlet.http.HttpServletRequest; 038import javax.servlet.http.HttpServletResponse; 039import javax.servlet.http.HttpSession; 040import java.io.IOException; 041import java.io.PrintWriter; 042import java.util.Map; 043 044/** 045 * Handles session timeouts for KRAD views based on the configured view session policy 046 * 047 * <p> 048 * IMPORTANT! In order to work correctly this filter should be the first filter invoked (even before the login 049 * filter) 050 * </p> 051 * 052 * @author Kuali Rice Team (rice.collab@kuali.org) 053 */ 054public class UifSessionTimeoutFilter implements Filter { 055 056 private int sessionTimeoutErrorCode = 403; 057 058 public void init(FilterConfig filterConfig) throws ServletException { 059 String timeoutErrorCode = filterConfig.getInitParameter("sessionTimeoutErrorCode"); 060 061 if (timeoutErrorCode != null) { 062 sessionTimeoutErrorCode = Integer.parseInt(timeoutErrorCode); 063 } 064 } 065 066 /** 067 * Checks for a session timeout and if one has occurred pulls the view session policy to determine whether 068 * a redirect needs to happen 069 * 070 * <p> 071 * To determine whether a session timeout has occurred, the filter looks for the existence of a request parameter 072 * named {@link org.kuali.rice.krad.uif.UifParameters#SESSION_ID}. If found it then compares that id to the id 073 * on the current session. If they are different, or a session does not currently exist a timeout is assumed. 074 * 075 * In addition, if a request was made for a form key and the view has session storage enabled, a check is made 076 * to verify the form manager contains a session form. If not this is treated like a session timeout 077 * </p> 078 * 079 * <p> 080 * If a timeout has occurred an attempt is made to resolve a view from the request (based on the view id or 081 * type parameters), then the associated {@link ViewSessionPolicy} is pulled which indicates how the timeout should 082 * be handled. This either results in doing a redirect or nothing 083 * </p> 084 * 085 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, 086 * javax.servlet.FilterChain) 087 */ 088 public void doFilter(ServletRequest request, ServletResponse response, 089 FilterChain filerChain) throws IOException, ServletException { 090 HttpServletRequest httpServletRequest = (HttpServletRequest) request; 091 HttpSession httpSession = (httpServletRequest).getSession(false); 092 093 boolean timeoutOccurred = false; 094 095 // compare session id in request to id on current session, if different or a session does not exist 096 // then assume a session timeout has occurred 097 if (request.getParameter(UifParameters.SESSION_ID) != null) { 098 String requestedSessionId = request.getParameter(UifParameters.SESSION_ID); 099 100 if ((httpSession == null) || !StringUtils.equals(httpSession.getId(), requestedSessionId)) { 101 timeoutOccurred = true; 102 } 103 } 104 105 String viewId = getViewIdFromRequest(httpServletRequest); 106 if (StringUtils.isBlank(viewId)) { 107 // can't retrieve a session policy if view id was not passed 108 filerChain.doFilter(request, response); 109 110 return; 111 } 112 113 // check for requested form key for a POST and if found and session storage is enabled for the 114 // view, verify the form is present in the form manager 115 boolean isGetRequest = RequestMethod.GET.name().equals(httpServletRequest.getMethod()); 116 117 String formKeyParam = request.getParameter(UifParameters.FORM_KEY); 118 if (StringUtils.isNotBlank(formKeyParam) && !isGetRequest && getViewDictionaryService().isSessionStorageEnabled( 119 viewId) && (httpSession != null)) { 120 UifFormManager uifFormManager = (UifFormManager) httpSession.getAttribute(UifParameters.FORM_MANAGER); 121 122 // if session form not found, treat like a session timeout 123 if ((uifFormManager != null) && !uifFormManager.hasSessionForm(formKeyParam)) { 124 timeoutOccurred = true; 125 } 126 } 127 128 // if no timeout occurred continue filter chain 129 if (!timeoutOccurred) { 130 filerChain.doFilter(request, response); 131 132 return; 133 } 134 135 // retrieve timeout policy associated with the view to determine what steps to take 136 ViewSessionPolicy sessionPolicy = getViewDictionaryService().getViewSessionPolicy(viewId); 137 138 if (sessionPolicy.isRedirectToHome() || StringUtils.isNotBlank(sessionPolicy.getRedirectUrl()) || sessionPolicy 139 .isRenderTimeoutView()) { 140 String redirectUrl = getRedirectUrl(sessionPolicy, httpServletRequest); 141 142 sendRedirect(httpServletRequest, (HttpServletResponse) response, redirectUrl); 143 } 144 } 145 146 /** 147 * Attempts to resolve a view id from the given request 148 * 149 * <p> 150 * First an attempt will be made to find the view id as a request parameter. If no such request parameter 151 * is found, the request will be looked at for view type information and a call will be made to the 152 * view service to find the view id by type 153 * </p> 154 * 155 * <p> 156 * If a view id is found it is stuck in the request as an attribute (under the key 157 * {@link org.kuali.rice.krad.uif.UifParameters#VIEW_ID}) for subsequent retrieval 158 * </p> 159 * 160 * @param request instance to resolve view id for 161 * @return view id if one is found, null if not found 162 */ 163 protected String getViewIdFromRequest(HttpServletRequest request) { 164 String viewId = request.getParameter(UifParameters.VIEW_ID); 165 166 if (StringUtils.isBlank(viewId)) { 167 String viewTypeName = request.getParameter(UifParameters.VIEW_TYPE_NAME); 168 169 UifConstants.ViewType viewType = null; 170 if (StringUtils.isNotBlank(viewTypeName)) { 171 viewType = UifConstants.ViewType.valueOf(viewTypeName); 172 } 173 174 if (viewType != null) { 175 @SuppressWarnings("unchecked") Map<String, String> parameterMap = 176 KRADUtils.translateRequestParameterMap(request.getParameterMap()); 177 viewId = getViewService().getViewIdForViewType(viewType, parameterMap); 178 } 179 } 180 181 if (StringUtils.isNotBlank(viewId)) { 182 request.setAttribute(UifParameters.VIEW_ID, viewId); 183 } 184 185 return viewId; 186 } 187 188 /** 189 * Inspects the given view session policy to determine how the request should be redirected 190 * 191 * <p> 192 * The request will either be redirected to the application home, a custom URL, the same request URL but 193 * modified to call the <code>sessionTimeout</code> method, or a redirect to show the session timeout view 194 * </p> 195 * 196 * @param sessionPolicy session policy instance to inspect 197 * @param httpServletRequest request instance for pulling parameters 198 * @return redirect URL or null if no redirect was configured 199 */ 200 protected String getRedirectUrl(ViewSessionPolicy sessionPolicy, HttpServletRequest httpServletRequest) { 201 String redirectUrl = null; 202 203 if (sessionPolicy.isRedirectToHome()) { 204 redirectUrl = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 205 KRADConstants.APPLICATION_URL_KEY); 206 } else if (StringUtils.isNotBlank(sessionPolicy.getRedirectUrl())) { 207 redirectUrl = sessionPolicy.getRedirectUrl(); 208 } else if (sessionPolicy.isRenderTimeoutView()) { 209 String kradUrl = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 210 KRADConstants.KRAD_URL_KEY); 211 redirectUrl = KRADUtils.buildViewUrl(kradUrl, KRADConstants.REQUEST_MAPPING_SESSION_TIMEOUT, 212 KRADConstants.SESSION_TIMEOUT_VIEW_ID); 213 } 214 215 return redirectUrl; 216 } 217 218 /** 219 * Sends a redirect request either through the standard http redirect mechanism, or by sending back 220 * an Ajax response indicating a redirect should occur 221 * 222 * @param httpServletRequest request instance the timeout occurred for 223 * @param httpServletResponse response object that redirect should occur on 224 * @param redirectUrl url to redirect to 225 * @throws IOException 226 */ 227 protected void sendRedirect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, 228 String redirectUrl) throws IOException { 229 // check for an ajax request since the redirects need to happen differently for them 230 boolean ajaxRequest = false; 231 232 String ajaxHeader = httpServletRequest.getHeader("x-requested-with"); 233 if ("XMLHttpRequest".equals(ajaxHeader)) { 234 ajaxRequest = true; 235 } 236 237 if (ajaxRequest) { 238 httpServletResponse.setContentType("text/html; charset=UTF-8"); 239 httpServletResponse.setCharacterEncoding("UTF-8"); 240 httpServletResponse.setStatus(sessionTimeoutErrorCode); 241 242 PrintWriter printWriter = httpServletResponse.getWriter(); 243 printWriter.print(redirectUrl); 244 245 printWriter.flush(); 246 } else { 247 httpServletResponse.sendRedirect(redirectUrl); 248 } 249 } 250 251 protected static ViewService getViewService() { 252 return KRADServiceLocatorWeb.getViewService(); 253 } 254 255 /** 256 * Retrieves implementation of the view dictionary service 257 * 258 * @return view dictionary service instance 259 */ 260 protected ViewDictionaryService getViewDictionaryService() { 261 return KRADServiceLocatorWeb.getViewDictionaryService(); 262 } 263 264 /** 265 * @see javax.servlet.Filter#destroy() 266 */ 267 public void destroy() { 268 // do nothing 269 } 270}