/** * Copyright (C) 2011 BonitaSoft S.A. * BonitaSoft, 31 rue Gustave Eiffel - 38000 Grenoble * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2.0 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package org.bonitasoft.forms.server.filter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang3.CharEncoding; import org.bonitasoft.engine.api.ProcessAPI; import org.bonitasoft.engine.api.TenantAPIAccessor; import org.bonitasoft.engine.bpm.process.ProcessDeploymentInfo; import org.bonitasoft.engine.bpm.process.ProcessDeploymentInfoSearchDescriptor; import org.bonitasoft.engine.search.Order; import org.bonitasoft.engine.search.SearchOptionsBuilder; import org.bonitasoft.engine.search.SearchResult; import org.bonitasoft.engine.session.APISession; import org.bonitasoft.forms.client.model.exception.ForbiddenProcessAccessException; import org.bonitasoft.forms.server.api.FormAPIFactory; import org.bonitasoft.forms.server.api.IFormWorkflowAPI; import org.bonitasoft.forms.server.exception.NoCredentialsInSessionException; /** * This filter transform the regular URL parameters into Hash parameters, with a generated formID. * * @author Chong Zhao */ public class BPMURLSupportFilter implements Filter { /** * the URL param for the form locale to use */ protected static final String FORM_LOCALE_URL_PARAM = "locale"; /** * the URL param for the token */ public static final String TOKEN_URL_PARAM = "token"; /** * user's domain URL parameter */ public static final String TENANT_PARAM = "tenant"; /** * user XP's UI mode parameter */ public static final String UI_MODE_PARAM = "ui"; /** * Theme parameter */ public static final String THEME_PARAM = "theme"; /** * ui parameter */ public static final String UI_PARAM = "ui"; /** * the GWT debug mode param */ public static final String GWT_DEBUG_PARAM = "gwt.codesvr"; /** * autologin parameter */ public static final String AUTO_LOGIN_PARAM = "autologin"; /** * old pattern task parameter */ public static final String TASK_PARAM = "task"; /** * old pattern process parameter */ public static final String PROCESS_PARAM = "process"; /** * old pattern process parameter */ public static final String PROCESS_NAME_PARAM = "processName"; /** * old pattern instance parameter */ public static final String INSTANCE_PARAM = "instance"; /** * old pattern recap parameter */ public static final String RECAP_PARAM = "recap"; /** * Ticket parameter for SSO */ public static final String TICKET_PARAM = "ticket"; /** * form type: entry */ public static final String ENTRY_FORM = "entry"; /** * form type: view */ public static final String VIEW_FORM = "view"; /** * form type: recap */ public static final String OVERVIEW_FORM = "recap"; /** * form id parameter */ public static final String FORM_ID_PARAM = "form"; /** * form id separator */ public static final String FORM_ID_SEPARATOR = "$"; /** * application url prefix */ public static final String APPLICATION_PREFIX = "/application"; /** * console home page keyword */ public static final String HOMEPAGE = "homepage"; /** * console form mode */ public static final String UI_FORM = "form"; /** * The engine API session param key name */ public static final String API_SESSION_PARAM_KEY = "apiSession"; /** * Logger */ private static final Logger LOGGER = Logger.getLogger(BPMURLSupportFilter.class.getName()); @Override public void init(final FilterConfig filterConfig) throws ServletException { } @Override @SuppressWarnings("unchecked") public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws ServletException, IOException { try { final HttpServletRequest httpServletRequest = (HttpServletRequest) request; final HttpServletResponse httpServletResponse = (HttpServletResponse) response; final Map<String, String[]> parameters = new HashMap<String, String[]>(httpServletRequest.getParameterMap()); final List<String> supportedParameterKeysList = Arrays.asList(FORM_LOCALE_URL_PARAM, TENANT_PARAM, UI_MODE_PARAM, THEME_PARAM, GWT_DEBUG_PARAM, TOKEN_URL_PARAM, AUTO_LOGIN_PARAM, TICKET_PARAM); final Set<String> parameterKeys = new HashSet<String>(parameters.keySet()); parameterKeys.removeAll(supportedParameterKeysList); if (!parameterKeys.isEmpty()) { if (parameterKeys.contains(PROCESS_NAME_PARAM)) { final long processDefinitionID = getProcessIDOfLatestVersion(parameters.get(PROCESS_NAME_PARAM), httpServletRequest); if (processDefinitionID != -1) { final String[] parameterValues = new String[1]; parameterValues[0] = String.valueOf(processDefinitionID); parameters.put(PROCESS_PARAM, parameterValues); } parameters.remove(PROCESS_NAME_PARAM); } if (!parameterKeys.contains(FORM_ID_PARAM) && isForm(httpServletRequest)) { final String formID[] = getFormIDFromRegularURLParameters(parameters, httpServletRequest); if (formID != null && formID.length > 0) { parameters.put(FORM_ID_PARAM, formID); } } final StringBuilder hashString = new StringBuilder(); final StringBuilder queryString = new StringBuilder(); for (final Entry<String, String[]> parameter : parameters.entrySet()) { final String key = parameter.getKey(); final String[] values = parameter.getValue(); if (supportedParameterKeysList.contains(key)) { buildQueryString(queryString, key, values); } else { buildQueryString(hashString, key, values); } } final StringBuilder redirectionURL = new StringBuilder(); redirectionURL.append(httpServletRequest.getRequestURI()); if (queryString.length() > 0) { redirectionURL.append("?"); redirectionURL.append(queryString); } if (hashString.length() > 0) { redirectionURL.append("#"); redirectionURL.append(hashString); } final String encodeRedirectURL = httpServletResponse.encodeRedirectURL(redirectionURL.toString()); httpServletResponse.sendRedirect(encodeRedirectURL); } else { response.setContentType(CharEncoding.UTF_8); filterChain.doFilter(request, response); } } catch (final Exception e) { LOGGER.log(Level.SEVERE, "Error while parsing the regular parameters into hash parameters."); throw new ServletException(e); } } protected void buildQueryString(final StringBuilder queryString, final String key, final String[] values) throws UnsupportedEncodingException { if (queryString.length() > 0) { queryString.append("&"); } queryString.append(key); queryString.append("="); if (values.length == 1) { queryString.append(URLEncoder.encode(values[0], CharEncoding.UTF_8)); } else if (values.length > 1) { final StringBuilder valuesList = new StringBuilder(); for (final String value : values) { if (valuesList.length() > 0) { valuesList.append(","); } valuesList.append(URLEncoder.encode(value, CharEncoding.UTF_8)); } queryString.append(valuesList); } } /** * Get the form id for any form. * * @param parameters * The regular parameters of current URL. * @param request * The current http servlet request. * @return formID */ protected String[] getFormIDFromRegularURLParameters(final Map<String, String[]> parameters, final HttpServletRequest request) { final String taskIDStr[] = parameters.get(TASK_PARAM); final String processIDStr[] = parameters.get(PROCESS_PARAM); final String instanceIDStr[] = parameters.get(INSTANCE_PARAM); final String recapParam[] = parameters.get(RECAP_PARAM); String formType = null; final String formID[] = new String[1]; boolean isRecap = false; if (recapParam != null) { isRecap = Boolean.parseBoolean(recapParam[0]); } final StringBuffer tempFormID = new StringBuffer(); try { final APISession session = getAPISession(request); if (taskIDStr != null) { final IFormWorkflowAPI workflowAPI = FormAPIFactory.getFormWorkflowAPI(); final long activityInstanceID = Long.parseLong(taskIDStr[0]); final String activityDefinitionUUID = workflowAPI.getActivityDefinitionUUIDFromActivityInstanceID(session, activityInstanceID); if (isRecap) { formType = OVERVIEW_FORM; } else { final boolean isTaskReady = workflowAPI.isTaskReady(session, activityInstanceID); formType = isTaskReady ? ENTRY_FORM : VIEW_FORM; } tempFormID.append(activityDefinitionUUID); tempFormID.append(FORM_ID_SEPARATOR); tempFormID.append(formType); } else if (processIDStr != null) { tempFormID.append(processIDStr[0]); tempFormID.append(FORM_ID_SEPARATOR); tempFormID.append(ENTRY_FORM); } else if (instanceIDStr != null) { final IFormWorkflowAPI workflowAPI = FormAPIFactory.getFormWorkflowAPI(); final long processInstanceID = Long.parseLong(instanceIDStr[0]); final long processDefinitionID = workflowAPI.getProcessDefinitionIDFromProcessInstanceID(session, processInstanceID); tempFormID.append(processDefinitionID); tempFormID.append(FORM_ID_SEPARATOR); formType = isRecap ? OVERVIEW_FORM : VIEW_FORM; tempFormID.append(formType); } else { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "No regular variables available in the URL for generating form id."); } } formID[0] = tempFormID.toString(); return formID; } catch (final Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Cannot get the formID with current URL parameters.", e); } return null; } } /** * Get the latest version of a given process name. * * @param processName * A regular URL parameter * @param request * The current http servlet request. * @return the ID of the latest version of the process * @throws ServletException */ protected long getProcessIDOfLatestVersion(final String[] processName, final HttpServletRequest request) throws ServletException { String errorMessage = null; long processUUID = -1; try { final APISession session = getAPISession(request); final long userId = session.getUserId(); final ProcessAPI processAPI = TenantAPIAccessor.getProcessAPI(session); final SearchOptionsBuilder builder = buildSearchOptions(0, 100, ProcessDeploymentInfoSearchDescriptor.DEPLOYMENT_DATE + " DESC", null); builder.filter(ProcessDeploymentInfoSearchDescriptor.NAME, processName[0]); final SearchResult<ProcessDeploymentInfo> deploymentInfoResult = processAPI.searchProcessDeploymentInfosCanBeStartedBy(userId, builder.done()); if (deploymentInfoResult != null && deploymentInfoResult.getCount() > 0) { processUUID = deploymentInfoResult.getResult().get(0).getProcessId(); } else { errorMessage = "Access denied: you do not have the privileges to see this page."; throw new ForbiddenProcessAccessException(); } return processUUID; } catch (final Exception e) { if (errorMessage == null) { errorMessage = "Error while getting the ID of the latest version process -- " + processName; } if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, errorMessage, e); } throw new ServletException(errorMessage, e); } } /** * Is console view mode * * @param request * @return isConsole flag */ protected boolean isForm(final HttpServletRequest request) { final String ui = request.getParameter(UI_PARAM); return UI_FORM.equals(ui); } /** * Retrieve the API session from the HTTP session * * @param request * the HTTP request * @return the tenantID * @throws NoCredentialsInSessionException */ protected APISession getAPISession(final HttpServletRequest request) throws NoCredentialsInSessionException { final HttpSession session = request.getSession(); // FIXME: Retrieve the API session in the user object final APISession apiSession = (APISession) session.getAttribute(API_SESSION_PARAM_KEY); if (apiSession == null) { final String errorMessage = "There is API session in the HTTP session."; if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, errorMessage); } throw new NoCredentialsInSessionException(errorMessage); } return apiSession; } /** * build SearchOptionsBuilder * * @param pageIndex * @param numberOfResults * @param sort * @param search * @return SearchOptionsBuilder object */ protected SearchOptionsBuilder buildSearchOptions(final int pageIndex, final int numberOfResults, final String sort, final String search) { final SearchOptionsBuilder builder = new SearchOptionsBuilder(pageIndex * numberOfResults, numberOfResults); if (sort != null) { final String[] order = sort.split(" "); if (order.length == 2) { builder.sort(order[0], Order.valueOf(order[1].toUpperCase())); } } if (search != null && !search.isEmpty()) { builder.searchTerm(search); } return builder; } @Override public void destroy() { } }