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