/** * Copyright (C) 2014 BonitaSoft S.A. * BonitaSoft, 32 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.console.common.server.login.filter; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.io.IOUtils; import org.bonitasoft.console.common.server.preferences.properties.DynamicPermissionsChecks; import org.bonitasoft.console.common.server.preferences.properties.PropertiesFactory; import org.bonitasoft.console.common.server.preferences.properties.ResourcesPermissionsMapping; import org.bonitasoft.console.common.server.utils.PermissionsBuilder; import org.bonitasoft.console.common.server.utils.SessionUtil; import org.bonitasoft.engine.api.PermissionAPI; import org.bonitasoft.engine.api.TenantAPIAccessor; import org.bonitasoft.engine.api.permission.APICallContext; import org.bonitasoft.engine.exception.BonitaException; import org.bonitasoft.engine.exception.BonitaHomeNotSetException; import org.bonitasoft.engine.exception.ExecutionException; import org.bonitasoft.engine.exception.NotFoundException; import org.bonitasoft.engine.exception.ServerAPIException; import org.bonitasoft.engine.exception.UnknownAPITypeException; import org.bonitasoft.engine.session.APISession; import org.bonitasoft.engine.session.PlatformSession; import org.bonitasoft.web.rest.server.framework.utils.RestRequestParser; import org.bonitasoft.web.toolkit.client.data.APIID; /** * @author Zhiheng Yang, Chong Zhao * @author Baptiste Mesta * @author Anthony Birembaut */ public class RestAPIAuthorizationFilter extends AbstractAuthorizationFilter { public static final String SCRIPT_TYPE_AUTHORIZATION_PREFIX = "check"; private static final String PLATFORM_API_URI_REGEXP = ".*(API|APIToolkit)/platform/.*"; protected static final String PLATFORM_SESSION_PARAM_KEY = "platformSession"; private final Boolean reload; public RestAPIAuthorizationFilter(final boolean reload) { this.reload = reload; } public RestAPIAuthorizationFilter() { reload = null;//will check property from security-config } @Override protected HttpServletRequest getRequest(final ServletRequest request) { //we need to use a MultiReadHttpServletRequest wrapper in order to be able to get the inputstream twice (in the filter and in the API servlet) return new MultiReadHttpServletRequest((HttpServletRequest) request); } @Override protected boolean checkValidCondition(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws ServletException { try { if (httpRequest.getRequestURI().matches(PLATFORM_API_URI_REGEXP)) { return platformAPIsCheck(httpRequest, httpResponse); } else { return tenantAPIsCheck(httpRequest, httpResponse); } } catch (final Exception e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } throw new ServletException(e); } } protected boolean tenantAPIsCheck(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws ServletException { final APISession apiSession = (APISession) httpRequest.getSession().getAttribute(SessionUtil.API_SESSION_PARAM_KEY); if (apiSession == null) { httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } else if (!checkPermissions(httpRequest)) { httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); return false; } else { return true; } } protected boolean platformAPIsCheck(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) { final PlatformSession platformSession = (PlatformSession) httpRequest.getSession().getAttribute(PLATFORM_SESSION_PARAM_KEY); if (platformSession != null) { return true; } else { httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } } protected boolean checkPermissions(final HttpServletRequest request) throws ServletException { final RestRequestParser restRequestParser = new RestRequestParser(request).invoke(); return checkPermissions(request, restRequestParser.getApiName(), restRequestParser.getResourceName(), restRequestParser.getResourceQualifiers()); } protected boolean checkPermissions(final HttpServletRequest request, final String apiName, final String resourceName, final APIID resourceQualifiers) throws ServletException { final String method = request.getMethod(); final HttpSession session = request.getSession(); @SuppressWarnings("unchecked") final Set<String> userPermissions = (Set<String>) session.getAttribute(SessionUtil.PERMISSIONS_SESSION_PARAM_KEY); final APISession apiSession = (APISession) session.getAttribute(SessionUtil.API_SESSION_PARAM_KEY); final Long tenantId = apiSession.getTenantId(); final boolean apiAuthorizationsCheckEnabled = isApiAuthorizationsCheckEnabled(tenantId); if (!apiAuthorizationsCheckEnabled || apiSession.isTechnicalUser()) { return true; } final String resourceQualifiersAsString = resourceQualifiers != null ? resourceQualifiers.toString() : null; final DynamicPermissionsChecks dynamicPermissionsChecks = getDynamicPermissionsChecks(tenantId); final Set<String> resourceAuthorizations = getDeclaredPermissions(apiName, resourceName, method, resourceQualifiers, dynamicPermissionsChecks); if (!resourceAuthorizations.isEmpty()) { //if there is a dynamic rule, use it to check the permissions final String requestBody = getRequestBody(request); final APICallContext apiCallContext = new APICallContext(method, apiName, resourceName, resourceQualifiersAsString, request.getQueryString(), requestBody); return dynamicCheck(apiCallContext, userPermissions, resourceAuthorizations, apiSession); } else { //if there is no dynamic rule, use the static permissions final ResourcesPermissionsMapping resourcesPermissionsMapping = getResourcesPermissionsMapping(tenantId); final Set<String> resourcePermissions = getDeclaredPermissions(apiName, resourceName, method, resourceQualifiers, resourcesPermissionsMapping); final APICallContext apiCallContext = new APICallContext(method, apiName, resourceName, resourceQualifiersAsString); return staticCheck(apiCallContext, userPermissions, resourcePermissions, apiSession.getUserName()); } } protected Set<String> getDeclaredPermissions(final String apiName, final String resourceName, final String method, final APIID resourceQualifiers, final ResourcesPermissionsMapping resourcesPermissionsMapping) { List<String> resourceQualifiersIds = null; if (resourceQualifiers != null) { resourceQualifiersIds = resourceQualifiers.getIds(); } Set<String> resourcePermissions = resourcesPermissionsMapping.getResourcePermissions(method, apiName, resourceName, resourceQualifiersIds); if (resourcePermissions.isEmpty()) { resourcePermissions = resourcesPermissionsMapping.getResourcePermissionsWithWildCard(method, apiName, resourceName, resourceQualifiersIds); } if (resourcePermissions.isEmpty()) { resourcePermissions = resourcesPermissionsMapping.getResourcePermissions(method, apiName, resourceName); } return resourcePermissions; } protected String getRequestBody(final HttpServletRequest request) throws ServletException { try { final ServletInputStream inputStream = request.getInputStream(); return IOUtils.toString(inputStream, request.getCharacterEncoding()); } catch (final IOException e) { throw new ServletException(e); } } protected boolean isApiAuthorizationsCheckEnabled(final Long tenantId) { return PropertiesFactory.getSecurityProperties(tenantId).isAPIAuthorizationsCheckEnabled(); } protected ResourcesPermissionsMapping getResourcesPermissionsMapping(final long tenantId) { return PropertiesFactory.getResourcesPermissionsMapping(tenantId); } protected DynamicPermissionsChecks getDynamicPermissionsChecks(final long tenantId) { return PropertiesFactory.getDynamicPermissionsChecks(tenantId); } protected boolean staticCheck(final APICallContext apiCallContext, final Set<String> permissionsOfUser, final Set<String> resourcePermissions, final String username) { for (final String resourcePermission : resourcePermissions) { if (permissionsOfUser.contains(resourcePermission)) { return true; } } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "Unauthorized access to " + apiCallContext.getMethod() + " " + apiCallContext.getApiName() + "/" + apiCallContext.getResourceName() + (apiCallContext.getResourceId() != null ? "/" + apiCallContext.getResourceId() : "") + " attempted by " + username + " required permissions: " + resourcePermissions); } return false; } protected boolean dynamicCheck(final APICallContext apiCallContext, final Set<String> userPermissions, final Set<String> resourceAuthorizations, final APISession apiSession) throws ServletException { checkResourceAuthorizationsSyntax(resourceAuthorizations); if (checkDynamicPermissionsWithUsername(resourceAuthorizations, apiSession) || checkDynamicPermissionsWithProfiles(resourceAuthorizations, userPermissions)) { return true; } final String resourceClassname = getResourceClassname(resourceAuthorizations); if (resourceClassname != null) { return checkDynamicPermissionsWithScript(apiCallContext, resourceClassname, apiSession); } return false; } protected boolean checkDynamicPermissionsWithScript(final APICallContext apiCallContext, final String resourceClassname, final APISession apiSession) throws ServletException { try { return executeScript(apiSession, resourceClassname, apiCallContext); } catch (final NotFoundException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to find the dynamic permissions script: " + resourceClassname, e); } return false; } catch (final ExecutionException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to execute the dynamic permissions script: " + resourceClassname, e); } return false; } catch (final BonitaException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to retrieve the permissions API", e); } throw new ServletException(e); } } protected boolean checkDynamicPermissionsWithProfiles(final Set<String> resourceAuthorizations, final Set<String> userPermissions) { final Set<String> profileAuthorizations = getResourceProfileAuthorizations(resourceAuthorizations); for (final String profileAuthorization : profileAuthorizations) { if (userPermissions.contains(profileAuthorization)) { return true; } } return false; } protected boolean checkDynamicPermissionsWithUsername(final Set<String> resourceAuthorizations, final APISession apiSession) { return resourceAuthorizations.contains(PermissionsBuilder.USER_TYPE_AUTHORIZATION_PREFIX + "|" + apiSession.getUserName()); } protected boolean checkResourceAuthorizationsSyntax(final Set<String> resourceAuthorizations) { boolean valid = true; for (final String resourceAuthorization : resourceAuthorizations) { if (!resourceAuthorization.matches("(" + PermissionsBuilder.USER_TYPE_AUTHORIZATION_PREFIX + "|" + PermissionsBuilder.PROFILE_TYPE_AUTHORIZATION_PREFIX + "|" + SCRIPT_TYPE_AUTHORIZATION_PREFIX + ")\\|.+")) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "Error while getting dynamic authoriations. Unknown syntax: " + resourceAuthorization + " defined in dynamic-permissions-checks.properties"); } valid = false; } } return valid; } protected String getResourceClassname(final Set<String> resourcePermissions) { String className = null; for (final String resourcePermission : resourcePermissions) { if (resourcePermission.startsWith(SCRIPT_TYPE_AUTHORIZATION_PREFIX + "|")) { className = resourcePermission.substring((SCRIPT_TYPE_AUTHORIZATION_PREFIX + "|").length()); } } return className; } protected Set<String> getResourceProfileAuthorizations(final Set<String> resourcePermissions) { final Set<String> profileAuthorizations = new HashSet<String>(); for (final String authorizedItem : resourcePermissions) { if (authorizedItem.startsWith(PermissionsBuilder.PROFILE_TYPE_AUTHORIZATION_PREFIX + "|")) { profileAuthorizations.add(authorizedItem); } } return profileAuthorizations; } protected boolean executeScript(final APISession apiSession, final String resourceClassname, final APICallContext apiCallContext) throws BonitaHomeNotSetException, ServerAPIException, UnknownAPITypeException, ExecutionException, NotFoundException { final PermissionAPI permissionAPI = TenantAPIAccessor.getPermissionAPI(apiSession); final boolean authorized = permissionAPI.checkAPICallWithScript(resourceClassname, apiCallContext, shouldReload(apiSession)); if (!authorized) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log( Level.FINEST, "Unauthorized access to " + apiCallContext.getMethod() + " " + apiCallContext.getApiName() + "/" + apiCallContext.getResourceName() + (apiCallContext.getResourceId() != null ? "/" + apiCallContext.getResourceId() : "") + " attempted by " + apiSession.getUserName() + " Permission script: " + resourceClassname); } } return authorized; } private boolean shouldReload(final APISession apiSession) { return reload == null ? PropertiesFactory.getSecurityProperties(apiSession.getTenantId()).isAPIAuthorizationsCheckInDebugMode() : reload; } }