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.controller;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.krad.UserSession;
021import org.kuali.rice.krad.service.CsrfService;
022import org.kuali.rice.krad.uif.UifConstants;
023import org.kuali.rice.krad.uif.UifParameters;
024import org.kuali.rice.krad.uif.util.ProcessLogger;
025import org.kuali.rice.krad.uif.view.ViewModel;
026import org.kuali.rice.krad.util.CsrfValidator;
027import org.kuali.rice.krad.util.GlobalVariables;
028import org.kuali.rice.krad.util.KRADUtils;
029import org.kuali.rice.krad.web.form.HistoryManager;
030import org.kuali.rice.krad.web.form.UifFormBase;
031import org.kuali.rice.krad.web.form.UifFormManager;
032import org.kuali.rice.krad.web.service.ModelAndViewService;
033import org.springframework.beans.factory.annotation.Autowired;
034import org.springframework.web.bind.annotation.RequestMethod;
035import org.springframework.web.method.HandlerMethod;
036import org.springframework.web.servlet.HandlerInterceptor;
037import org.springframework.web.servlet.ModelAndView;
038
039import javax.servlet.http.HttpServletRequest;
040import javax.servlet.http.HttpServletResponse;
041
042/**
043 * Spring controller intercepter for KRAD controllers.
044 *
045 * <p>Provides infrastructure for preparing the form and view before and after the controller is invoked.
046 * Included in this is form session management and preparation of the view for rendering</p>
047 *
048 * @author Kuali Rice Team (rice.collab@kuali.org)
049 */
050public class UifControllerHandlerInterceptor implements HandlerInterceptor {
051    private static final Logger LOG = Logger.getLogger(UifControllerHandlerInterceptor.class);
052
053    @Autowired
054    private ModelAndViewService modelAndViewService;
055
056    @Autowired
057    private CsrfService csrfService;
058
059    /**
060     * Before the controller executes the user session is set on GlobalVariables
061     * and messages are cleared, in addition setup for the history manager and a check on missing session
062     * forms is performed.
063     *
064     * {@inheritDoc}
065     */
066    @Override
067    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
068            Object handler) throws Exception {
069        checkHandlerMethodAccess(request, handler);
070
071        if (!getCsrfService().validateCsrfIfNecessary(request, response)) {
072            return false;
073        }
074
075        final UserSession session = KRADUtils.getUserSessionFromRequest(request);
076
077        GlobalVariables.setUserSession(session);
078        GlobalVariables.clear();
079
080        createUifFormManagerIfNecessary(request);
081
082        // add the HistoryManager for storing HistoryFlows to the session
083        if (request.getSession().getAttribute(UifConstants.HistoryFlow.HISTORY_MANAGER) == null) {
084            request.getSession().setAttribute(UifConstants.HistoryFlow.HISTORY_MANAGER, new HistoryManager());
085        }
086
087        ProcessLogger.trace("pre-handle");
088
089        return true;
090    }
091
092    /**
093     * Checks whether access is allowed for the requested controller method.
094     *
095     * <p>First a check is done on the method to determine whether it contains the annotation
096     * {@link org.kuali.rice.krad.web.controller.MethodAccessible}. If so, access is allowed. If the
097     * annotation is not present, data from the posted view (if any) is referenced to determine
098     * whether the method was configured as an accessible method to call.</p>
099     *
100     * <p>If method access is not allowed, a {@link org.kuali.rice.krad.web.controller.MethodAccessException}
101     * is thrown.</p>
102     *
103     * @param request HTTP request (used to retrieve parameters)
104     * @param handler handler method that was determined based on the request and mappings
105     * @throws Exception
106     */
107    protected void checkHandlerMethodAccess(HttpServletRequest request, Object handler) throws Exception {
108        String requestMethod = request.getMethod();
109
110        // if it is a GET request then we allow without any check
111        if(requestMethod.equalsIgnoreCase(RequestMethod.GET.name())) {
112            return;
113        }
114
115        HandlerMethod handlerMethod = (HandlerMethod) handler;
116        MethodAccessible methodAccessible = handlerMethod.getMethodAnnotation(MethodAccessible.class);
117
118        // if accessible by annotation then return, otherwise go on to check view configuration
119        if (methodAccessible != null) {
120            return;
121        }
122
123        boolean isMethodAccessible = checkForMethodAccess(request);
124
125        if (!isMethodAccessible) {
126            throw new MethodAccessException(handlerMethod.getBeanType(), handlerMethod.getMethod().getName());
127        }
128    }
129
130    /**
131     * Checks whether access to the handler method is allowed based available methods or accessible methods
132     * on view configuration.
133     *
134     * <p>Since this method is invoked before the request form is setup, we need to retrieve the session form
135     * form the form manager. In the case of missing post data (GET requests), view method access is not
136     * granted.</p>
137     *
138     * @param request HTTP request to retrieve parameters from
139     * @return boolean true if method access is allowed based on the view, false if not
140     */
141    protected boolean checkForMethodAccess(HttpServletRequest request) {
142        String methodToCall = request.getParameter(UifParameters.METHOD_TO_CALL);
143
144        // if method to call is blank, we will assume they are using other strategies to map controller
145        // methods, and therefore using custom access management
146        if (StringUtils.isBlank(methodToCall)) {
147            return true;
148        }
149
150        UifFormManager uifFormManager = (UifFormManager) request.getSession().getAttribute(UifParameters.FORM_MANAGER);
151        UifFormBase form = null;
152
153        String formKeyParam = request.getParameter(UifParameters.FORM_KEY);
154        if (StringUtils.isNotBlank(formKeyParam) && (uifFormManager != null)) {
155            form = uifFormManager.getSessionForm(formKeyParam);
156        }
157
158        // if we don't have the view post data, there is nothing to validate
159        if ((form == null) || (form.getViewPostMetadata() == null)) {
160            return true;
161        }
162
163        // if the method to call is listed as a method in either the available methods to call or the
164        // view's accessible methods to call, then return true
165        return !form.getViewPostMetadata().getAvailableMethodToCalls().contains(methodToCall) || ((form
166                .getViewPostMetadata().getAccessibleMethodToCalls() != null) && form.getViewPostMetadata()
167                .getAccessibleMethodToCalls().contains(methodToCall));
168    }
169
170    /**
171     * Checks if a form manager is present in the session, and if not creates a form manager and adds to the
172     * session and global variables.
173     *
174     * @param request http request being handled
175     */
176    protected void createUifFormManagerIfNecessary(HttpServletRequest request) {
177        UifFormManager uifFormManager = (UifFormManager) request.getSession().getAttribute(UifParameters.FORM_MANAGER);
178        if (uifFormManager == null) {
179            uifFormManager = new UifFormManager();
180            request.getSession().setAttribute(UifParameters.FORM_MANAGER, uifFormManager);
181        }
182
183        // add form manager to GlobalVariables for easy reference by other controller methods
184        GlobalVariables.setUifFormManager(uifFormManager);
185    }
186
187    /**
188     * After the controller logic is executed, the form is placed into session and the corresponding view
189     * is prepared for rendering.
190     *
191     * {@inheritDoc}
192     */
193    @Override
194    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
195            ModelAndView modelAndView) throws Exception {
196        if (request.getAttribute(UifParameters.Attributes.VIEW_LIFECYCLE_COMPLETE) == null) {
197            getModelAndViewService().prepareView(request, modelAndView);
198        }
199
200        if ((modelAndView != null) && (modelAndView.getModelMap() != null)) {
201            Object model = modelAndView.getModelMap().get(UifConstants.DEFAULT_MODEL_NAME);
202            if ((model != null) && (model instanceof ViewModel)) {
203                ((ViewModel) model).preRender(request);
204            }
205        }
206
207        ProcessLogger.trace("post-handle");
208    }
209
210    /**
211     * After the view is rendered remove the view to reduce the size of the form storage in memory.
212     *
213     * {@inheritDoc}
214     */
215    @Override
216    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
217            Exception ex) throws Exception {
218        ProcessLogger.trace("after-completion");
219
220        UifFormManager uifFormManager = (UifFormManager) request.getSession().getAttribute(UifParameters.FORM_MANAGER);
221        UifFormBase uifForm = (UifFormBase) request.getAttribute(UifConstants.REQUEST_FORM);
222
223        if ((uifForm == null) || (uifForm.getView() == null)) {
224            return;
225        }
226
227        // remove the session transient variables from the request form before adding it to the list of
228        // Uif session forms
229        boolean persistFormToSession = uifForm.getView().isPersistFormToSession();
230        if (persistFormToSession && (uifFormManager != null)) {
231            uifFormManager.purgeForm(uifForm);
232            uifFormManager.addSessionForm(uifForm);
233        }
234
235        uifForm.setView(null);
236
237        ProcessLogger.trace("after-completion-end");
238    }
239
240    protected ModelAndViewService getModelAndViewService() {
241        return modelAndViewService;
242    }
243
244    public void setModelAndViewService(ModelAndViewService modelAndViewService) {
245        this.modelAndViewService = modelAndViewService;
246    }
247
248    protected CsrfService getCsrfService() {
249        return csrfService;
250    }
251
252    public void setCsrfService(CsrfService csrfService) {
253        this.csrfService = csrfService;
254    }
255
256}