/*******************************************************************************
* Copyright (C) 2015 BonitaSoft S.A.
* BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation
* version 2.1 of the License.
* This library 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 Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301, USA.
******************************************************************************/
package org.bonitasoft.livingapps;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.bonitasoft.console.common.server.page.CustomPageAuthorizationsHelper;
import org.bonitasoft.console.common.server.page.CustomPageRequestModifier;
import org.bonitasoft.console.common.server.page.CustomPageService;
import org.bonitasoft.console.common.server.page.GetUserRightsHelper;
import org.bonitasoft.console.common.server.page.PageRenderer;
import org.bonitasoft.console.common.server.page.ResourceRenderer;
import org.bonitasoft.console.common.server.page.extension.PageResourceProviderImpl;
import org.bonitasoft.console.common.server.utils.BonitaHomeFolderAccessor;
import org.bonitasoft.console.common.server.utils.SessionUtil;
import org.bonitasoft.engine.api.ApplicationAPI;
import org.bonitasoft.engine.api.PageAPI;
import org.bonitasoft.engine.api.TenantAPIAccessor;
import org.bonitasoft.engine.business.application.ApplicationPageNotFoundException;
import org.bonitasoft.engine.exception.BonitaException;
import org.bonitasoft.engine.exception.BonitaHomeNotSetException;
import org.bonitasoft.engine.exception.ServerAPIException;
import org.bonitasoft.engine.exception.UnknownAPITypeException;
import org.bonitasoft.engine.page.PageNotFoundException;
import org.bonitasoft.engine.session.APISession;
public class LivingApplicationPageServlet extends HttpServlet {
/**
* Logger
*/
private static Logger LOGGER = Logger.getLogger(LivingApplicationPageServlet.class.getName());
/**
* uuid
*/
private static final long serialVersionUID = -5410859017103815654L;
public static final String RESOURCE_PATH_SEPARATOR = "/content";
public static final String API_PATH_SEPARATOR = "/API";
public static final String THEME_PATH_SEPARATOR = "/theme";
protected ResourceRenderer resourceRenderer = new ResourceRenderer();
protected PageRenderer pageRenderer = new PageRenderer(resourceRenderer);
protected BonitaHomeFolderAccessor bonitaHomeFolderAccessor = new BonitaHomeFolderAccessor();
protected CustomPageRequestModifier customPageRequestModifier = new CustomPageRequestModifier();
@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
final String pathInfo = request.getPathInfo();
final List<String> pathSegments = resourceRenderer.getPathSegments(pathInfo);
if (isValidPathForToken(API_PATH_SEPARATOR, pathSegments)) {
//Support relative calls to the REST API from the forms using ../API/
final String apiPath = pathInfo.substring(pathInfo.indexOf(API_PATH_SEPARATOR + "/"));
request.getRequestDispatcher(apiPath).forward(request, response);
} else {
super.service(request, response);
}
}
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
final String pathInfo = request.getPathInfo();
final HttpSession session = request.getSession();
final APISession apiSession = (APISession) session.getAttribute(SessionUtil.API_SESSION_PARAM_KEY);
// Check if requested URL is missing final slash (necessary in order to be able to use relative URLs for resources)
if (pathInfo.endsWith(RESOURCE_PATH_SEPARATOR)) {
customPageRequestModifier.redirectToValidPageUrl(request, response);
return;
}
String appToken = null;
String pageToken = null;
String customPageName = null;
String resourcePath = null;
//Validate mapping key contain "AppToken/PageToken/" and at least one more segment
final List<String> pathSegments = resourceRenderer.getPathSegments(pathInfo);
if (pathSegments.size() >= 3) {
appToken = pathSegments.get(0);
pageToken = pathSegments.get(1);
customPageName = getCustomPageName(appToken, pageToken, apiSession, response);
if (isValidPathForToken(RESOURCE_PATH_SEPARATOR, pathSegments)) {
final String pageMapping = "/" + appToken + "/" + pageToken + RESOURCE_PATH_SEPARATOR + "/";
if (pathInfo.length() > pageMapping.length()) {
resourcePath = pathInfo.substring(pageMapping.length());
}
try {
if (resourcePath == null || isNotResourcePath(resourcePath)) {
if (!isAuthorized(apiSession, appToken, customPageName)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "User not Authorized");
return;
}
pageRenderer.displayCustomPage(request, response, apiSession, customPageName);
} else {
final File resourceFile = getResourceFile(resourcePath, customPageName, apiSession);
pageRenderer.ensurePageFolderIsPresent(apiSession, pageRenderer.getPageResourceProvider(customPageName, apiSession.getTenantId()));
resourceRenderer.renderFile(request, response, resourceFile, apiSession);
}
} catch (final Exception e) {
handleException(customPageName, e);
}
} else if (isValidPathForToken(THEME_PATH_SEPARATOR, pathSegments)) {
//Support relative calls to the THEME from the application page using ../theme/
final String themePath = pathInfo.substring(pathInfo.indexOf(THEME_PATH_SEPARATOR + "/"));
request.getRequestDispatcher("/apps/" + appToken + themePath).forward(request, response);
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"One of the separator '/content', '/theme' or '/API' is expected in the URL after the application token and the page token.");
}
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"The info path is suppose to contain the application token, the page token and one of the separator '/content', '/theme' or '/API'.");
}
}
private Long getCustomPageId(final String appToken, final String pageToken, final APISession apiSession, final HttpServletResponse response) throws IOException, ServletException {
try {
return getApplicationApi(apiSession).getApplicationPage(appToken, pageToken).getPageId();
} catch (final ApplicationPageNotFoundException e) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Error while trying to render the application page " + appToken+ "/" +pageToken, e);
}
response.sendError(HttpServletResponse.SC_NOT_FOUND,
"Cannot found the page" + pageToken + "for the application" + appToken + ".");
} catch (final Exception e) {
handleException(appToken + "/" + pageToken, e);
}
return null;
}
private String getCustomPageName(final String appToken, final String pageToken, final APISession apiSession, final HttpServletResponse response) throws ServletException, IOException {
try {
final Long customPageId = getCustomPageId(appToken, pageToken,apiSession,response);
return getPageApi(apiSession).getPage(customPageId).getName();
} catch (final PageNotFoundException e) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Error while trying to render the application page " + appToken+ "/" +pageToken, e);
}
response.sendError(HttpServletResponse.SC_NOT_FOUND,
"Cannot found the page"+pageToken+"for the application"+appToken+".");
} catch (final Exception e) {
handleException(appToken + "/" + pageToken, e);
}
return "";
}
private boolean isValidPathForToken(final String TokenSeparator, final List<String> pathSegments) {
return pathSegments.size() > 2 && pathSegments.get(2).equals(TokenSeparator.substring(1));
}
private boolean isNotResourcePath(final String resourcePath) {
return resourcePath == null || CustomPageService.PAGE_INDEX_FILENAME.equals(resourcePath)
|| CustomPageService.PAGE_CONTROLLER_FILENAME.equals(resourcePath) || CustomPageService.PAGE_INDEX_NAME.equals(resourcePath);
}
private File getResourceFile(final String resourcePath, final String pageName, final APISession apiSession) throws IOException, BonitaException {
final PageResourceProviderImpl pageResourceProvider = pageRenderer.getPageResourceProvider(pageName, apiSession.getTenantId());
final File resourceFile = new File(pageResourceProvider.getPageDirectory(), CustomPageService.RESOURCES_PROPERTY + File.separator
+ resourcePath);
if (!bonitaHomeFolderAccessor.isInFolder(resourceFile, pageResourceProvider.getPageDirectory())) {
throw new BonitaException("Unauthorized access to the file " + resourcePath);
}
return resourceFile;
}
private boolean isAuthorized(final APISession apiSession, final String appToken, final String pageName) throws BonitaException {
return getCustomPageAuthorizationsHelper(apiSession).isPageAuthorized(appToken, pageName);
}
private void handleException(final String pageName, final Exception e) throws ServletException {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Error while trying to render the application page " + pageName, e);
}
throw new ServletException(e.getMessage());
}
protected ApplicationAPI getApplicationApi(final APISession apiSession) throws BonitaHomeNotSetException, ServerAPIException, UnknownAPITypeException {
return TenantAPIAccessor.getLivingApplicationAPI(apiSession);
}
protected PageAPI getPageApi(final APISession apiSession) throws BonitaHomeNotSetException, ServerAPIException, UnknownAPITypeException {
return TenantAPIAccessor.getCustomPageAPI(apiSession);
}
protected CustomPageAuthorizationsHelper getCustomPageAuthorizationsHelper(final APISession apiSession) throws BonitaHomeNotSetException,
ServerAPIException, UnknownAPITypeException {
return new CustomPageAuthorizationsHelper(new GetUserRightsHelper(apiSession),
TenantAPIAccessor.getLivingApplicationAPI(apiSession), TenantAPIAccessor.getCustomPageAPI(apiSession));
}
}