/* * Copyright 2008-2014 by Emeric Vernat * * This file is part of Java Melody. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.bull.javamelody; import java.io.IOException; import java.security.Principal; import java.util.Arrays; import java.util.List; 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; /** * Filter of monitoring JavaMelody for JIRA/Bamboo/Confluence with security check for system administrator. * @author Emeric Vernat */ public class JiraMonitoringFilter extends PluginMonitoringFilter { private static final boolean PLUGIN_AUTHENTICATION_DISABLED = Boolean.parseBoolean(System .getProperty("javamelody.plugin-authentication-disabled")); // valeur de com.atlassian.jira.security.Permissions.SYSTEM_ADMIN private static final int SYSTEM_ADMIN = 44; // valeur de DefaultAuthenticator.LOGGED_IN_KEY private static final String LOGGED_IN_KEY = "seraph_defaultauthenticator_user"; private static final List<String> JIRA_USER_CLASSES = Arrays.asList( // since JIRA 6, but exists in JIRA 5.2: "com.atlassian.jira.user.ApplicationUser", // since JIRA 5: "com.atlassian.crowd.embedded.api.User", // before JIRA 5: "com.opensymphony.user.User"); // initialisation ici et non dans la méthode init, car on ne sait pas très bien // quand la méthode init serait appelée dans les systèmes de plugins private final boolean jira = isJira(); private final boolean confluence = isConfluence(); private final boolean bamboo = isBamboo(); private boolean confluenceGetUserByNameExists = true; // on suppose true au départ /** {@inheritDoc} */ @Override public void init(FilterConfig config) throws ServletException { super.init(config); if (jira) { LOG.debug("JavaMelody is monitoring JIRA"); } else if (confluence) { LOG.debug("JavaMelody is monitoring Confluence"); } else if (bamboo) { LOG.debug("JavaMelody is monitoring Bamboo"); } else { LOG.debug("JavaMelody is monitoring unknown, access to monitoring reports is not secured by JavaMelody"); } if (PLUGIN_AUTHENTICATION_DISABLED) { LOG.debug("Authentication for monitoring reports has been disabled"); } final String analyticsDisabled = "javamelody.analytics-disabled"; if (System.getProperty(analyticsDisabled) != null || config.getServletContext().getInitParameter(analyticsDisabled) != null) { System.setProperty("javamelody.analytics-id", "disabled"); } } /** {@inheritDoc} */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!(request instanceof HttpServletRequest)) { super.doFilter(request, response, chain); return; } final HttpServletRequest httpRequest = (HttpServletRequest) request; final HttpServletResponse httpResponse = (HttpServletResponse) response; if (httpRequest.getRequestURI().equals(getMonitoringUrl(httpRequest)) && hasNotPermission(httpRequest, httpResponse)) { return; } putRemoteUserInSession(httpRequest); super.doFilter(request, response, chain); } private void putRemoteUserInSession(HttpServletRequest httpRequest) { final HttpSession session = httpRequest.getSession(false); if (session != null && session.getAttribute(SessionInformations.SESSION_REMOTE_USER) == null) { // si session null, la session n'est pas encore créée (et ne le sera peut-être jamais), try { final Object user = getUser(session); // objet utilisateur, peut être null if (user instanceof Principal) { final String remoteUser = ((Principal) user).getName(); session.setAttribute(SessionInformations.SESSION_REMOTE_USER, remoteUser); } } catch (final Exception e) { // tant pis return; } } } private boolean hasNotPermission(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { return !PLUGIN_AUTHENTICATION_DISABLED && (jira && !checkJiraAdminPermission(httpRequest, httpResponse) || confluence && !checkConfluenceAdminPermission(httpRequest, httpResponse) || bamboo && !checkBambooAdminPermission(httpRequest, httpResponse)); } private boolean checkJiraAdminPermission(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { // only the administrator can view the monitoring report final Object user = getUser(httpRequest); if (user == null) { // si non authentifié, on redirige vers la page de login en indiquant la page // d'origine (sans le contexte) à afficher après le login final String destination = getMonitoringUrl(httpRequest).substring( httpRequest.getContextPath().length()); httpResponse.sendRedirect("login.jsp?os_destination=" + destination); return false; } if (!hasJiraSystemAdminPermission(user)) { // si authentifié mais sans la permission system admin, alors Forbidden httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden access"); return false; } return true; } private boolean checkConfluenceAdminPermission(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { // only the administrator can view the monitoring report final Object user = getUser(httpRequest); if (user == null) { // si non authentifié, on redirige vers la page de login en indiquant la page // d'origine (sans le contexte) à afficher après le login final String destination = getMonitoringUrl(httpRequest).substring( httpRequest.getContextPath().length()); httpResponse.sendRedirect("login.action?os_destination=" + destination); return false; } if (!hasConfluenceAdminPermission(user)) { // si authentifié mais sans la permission system admin, alors Forbidden httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden access"); return false; } return true; } private boolean checkBambooAdminPermission(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { // only the administrator can view the monitoring report final Object user = getUser(httpRequest); if (user == null) { // si non authentifié, on redirige vers la page de login en indiquant la page // d'origine (sans le contexte) à afficher après le login final String destination = getMonitoringUrl(httpRequest).substring( httpRequest.getContextPath().length()); httpResponse.sendRedirect("userlogin!default.action?os_destination=" + destination); return false; } if (!hasBambooAdminPermission(user)) { // si authentifié mais sans la permission admin, alors Forbidden httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden access"); return false; } return true; } private static boolean hasJiraSystemAdminPermission(Object user) { try { final Class<?> managerFactoryClass = Class.forName("com.atlassian.jira.ManagerFactory"); // on travaille par réflexion car la compilation normale introduirait une dépendance // trop compliquée et trop lourde à télécharger pour maven final Object permissionManager = managerFactoryClass.getMethod("getPermissionManager") .invoke(null); Exception firstException = null; // selon la version de JIRA, on essaye les différentes classes possibles du user for (final String className : JIRA_USER_CLASSES) { try { final Class<?> userClass = Class.forName(className); final Boolean result = (Boolean) permissionManager.getClass() .getMethod("hasPermission", Integer.TYPE, userClass) .invoke(permissionManager, SYSTEM_ADMIN, user); return result; } catch (final Exception e) { if (firstException == null) { firstException = e; } continue; } } // aucune classe n'a fonctionné throw firstException; } catch (final Exception e) { throw new IllegalStateException(e); } // return user != null // && com.atlassian.jira.ManagerFactory.getPermissionManager().hasPermission( // SYSTEM_ADMIN, (com.opensymphony.user.User) user); } private static boolean hasConfluenceAdminPermission(Object user) { try { final Class<?> containerManagerClass = Class .forName("com.atlassian.spring.container.ContainerManager"); final Class<?> userClass = Class.forName("com.atlassian.user.User"); // on travaille par réflexion car la compilation normale introduirait une dépendance // trop compliquée et trop lourde à télécharger pour maven final Object permissionManager = containerManagerClass.getMethod("getComponent", String.class).invoke(null, "permissionManager"); final Boolean result = (Boolean) permissionManager.getClass() .getMethod("isConfluenceAdministrator", userClass) .invoke(permissionManager, user); return result; } catch (final Exception e) { throw new IllegalStateException(e); } // return user != null // && com.atlassian.spring.container.ContainerManager.getComponent("permissionManager"). // isConfluenceAdministrator((com.opensymphony.user.User) user); } private static boolean hasBambooAdminPermission(Object user) { try { final Class<?> containerManagerClass = Class .forName("com.atlassian.spring.container.ContainerManager"); // on travaille par réflexion car la compilation normale introduirait une dépendance // trop compliquée et trop lourde à télécharger pour maven final Object bambooPermissionManager = containerManagerClass.getMethod("getComponent", String.class).invoke(null, "bambooPermissionManager"); Boolean result; try { // since Bamboo 3.1 (issue 192): result = (Boolean) bambooPermissionManager.getClass() .getMethod("isSystemAdmin", String.class) .invoke(bambooPermissionManager, user.toString()); } catch (final NoSuchMethodException e) { // before Bamboo 3.1 (issue 192): final Class<?> globalApplicationSecureObjectClass = Class .forName("com.atlassian.bamboo.security.GlobalApplicationSecureObject"); final Object globalApplicationSecureObject = globalApplicationSecureObjectClass .getField("INSTANCE").get(null); result = (Boolean) bambooPermissionManager .getClass() .getMethod("hasPermission", String.class, String.class, Object.class) .invoke(bambooPermissionManager, user.toString(), "ADMIN", globalApplicationSecureObject); } return result; } catch (final Exception e) { throw new IllegalStateException(e); } // return user != null // && com.atlassian.spring.container.ContainerManager.getComponent("bambooPermissionManager"). // hasPermission(username, "ADMIN", GlobalApplicationSecureObject.INSTANCE); } private Object getUser(HttpServletRequest httpRequest) { final HttpSession session = httpRequest.getSession(false); return getUser(session); } private Object getUser(HttpSession session) { // ceci fonctionne dans JIRA et dans Confluence (et Bamboo ?) if (session == null) { return null; } Object result = session.getAttribute(LOGGED_IN_KEY); if (confluence) { if (result != null && "com.atlassian.confluence.user.SessionSafePrincipal".equals(result .getClass().getName())) { // since confluence 4.1.4 (or 4.1.?) final String userName = result.toString(); // note: httpRequest.getRemoteUser() null in general try { final Class<?> containerManagerClass = Class .forName("com.atlassian.spring.container.ContainerManager"); final Object userAccessor = containerManagerClass.getMethod("getComponent", String.class).invoke(null, "userAccessor"); result = userAccessor.getClass().getMethod("getUser", String.class) .invoke(userAccessor, userName); } catch (final Exception e) { throw new IllegalStateException(e); } } else if (result instanceof Principal && confluenceGetUserByNameExists) { // since confluence 5.2 or 5.3 final String userName = ((Principal) result).getName(); try { final Class<?> containerManagerClass = Class .forName("com.atlassian.spring.container.ContainerManager"); final Object userAccessor = containerManagerClass.getMethod("getComponent", String.class).invoke(null, "userAccessor"); // getUser deprecated, use getUserByName as said in: // https://docs.atlassian.com/atlassian-confluence/5.3.1/com/atlassian/confluence/user/UserAccessor.html try { result = userAccessor.getClass().getMethod("getUserByName", String.class) .invoke(userAccessor, userName); } catch (final NoSuchMethodException e) { // getUserByName does not exist in old Confluence versions (3.5.13 for example) confluenceGetUserByNameExists = false; } } catch (final Exception e) { throw new IllegalStateException(e); } } } return result; } private static boolean isJira() { try { Class.forName("com.atlassian.jira.ManagerFactory"); return true; } catch (final ClassNotFoundException e) { return false; } } private static boolean isConfluence() { try { Class.forName("com.atlassian.confluence.security.PermissionManager"); return true; } catch (final ClassNotFoundException e) { return false; } } private static boolean isBamboo() { try { Class.forName("com.atlassian.bamboo.security.BambooPermissionManager"); return true; } catch (final ClassNotFoundException e) { return false; } } }