/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * 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; either version 2.1 of the License, or (at your option) * any later version. * * 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. */ package com.liferay.portal.security.pacl.checker; import com.liferay.portal.bean.BeanLocatorImpl; import com.liferay.portal.dao.orm.hibernate.DynamicQueryFactoryImpl; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.portlet.PortletClassLoaderUtil; import com.liferay.portal.kernel.security.pacl.permission.PortalRuntimePermission; import com.liferay.portal.kernel.service.BaseLocalServiceImpl; import com.liferay.portal.kernel.service.BaseServiceImpl; import com.liferay.portal.kernel.service.persistence.impl.BasePersistenceImpl; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.PortalClassLoaderUtil; import com.liferay.portal.kernel.util.SetUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.security.pacl.Reflection; import com.liferay.portal.template.TemplateContextHelper; import java.lang.reflect.Modifier; import java.security.Permission; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Brian Wing Shun Chan * @author Raymond Augé */ public class PortalRuntimeChecker extends BaseChecker { @Override public void afterPropertiesSet() { initClassLoaderReferenceIds(); initExpandoBridgeClassNames(); initGetBeanPropertyClassNames(); initPortletBagPoolPortletIds(); initSearchEngineIds(); initSetBeanPropertyClassNames(); initThreadPoolExecutorNames(); } @Override public AuthorizationProperty generateAuthorizationProperty( Object... arguments) { if ((arguments == null) || (arguments.length != 1) || !(arguments[0] instanceof Permission)) { return null; } PortalRuntimePermission portalRuntimePermission = (PortalRuntimePermission)arguments[0]; String name = portalRuntimePermission.getShortName(); String servletContextName = portalRuntimePermission.getServletContextName(); String subject = portalRuntimePermission.getSubject(); String property = portalRuntimePermission.getProperty(); String key = null; String value = subject; if (name.startsWith(PORTAL_RUNTIME_PERMISSION_GET_CLASSLOADER)) { key = "security-manager-class-loader-reference-ids"; } else if (name.equals(PORTAL_RUNTIME_PERMISSION_EXPANDO_BRIDGE)) { key = "security-manager-expando-bridge"; } else if (name.equals(PORTAL_RUNTIME_PERMISSION_GET_BEAN_PROPERTY)) { StringBundler sb = new StringBundler(4); sb.append("security-manager-get-bean-property"); sb.append(StringPool.OPEN_BRACKET); sb.append(servletContextName); sb.append(StringPool.CLOSE_BRACKET); key = sb.toString(); if (Validator.isNotNull(property)) { value = value + StringPool.POUND + property; } } else if (name.equals(PORTAL_RUNTIME_PERMISSION_PORTLET_BAG_POOL)) { key = "security-manager-portlet-bag-pool-portlet-ids"; } else if (name.equals(PORTAL_RUNTIME_PERMISSION_SEARCH_ENGINE)) { key = "security-manager-search-engine-ids"; } else if (name.equals(PORTAL_RUNTIME_PERMISSION_SET_BEAN_PROPERTY)) { StringBundler sb = new StringBundler(4); sb.append("security-manager-set-bean-property"); sb.append(StringPool.OPEN_BRACKET); sb.append(servletContextName); sb.append(StringPool.CLOSE_BRACKET); key = sb.toString(); if (Validator.isNotNull(property)) { value = value + StringPool.POUND + property; } } else if (name.equals(PORTAL_RUNTIME_PERMISSION_THREAD_POOL_EXECUTOR)) { key = "security-manager-thread-pool-executor-names"; } else { return null; } AuthorizationProperty authorizationProperty = new AuthorizationProperty(); authorizationProperty.setKey(key); authorizationProperty.setValue(value); return authorizationProperty; } @Override public boolean implies(Permission permission) { PortalRuntimePermission portalRuntimePermission = (PortalRuntimePermission)permission; String name = portalRuntimePermission.getShortName(); String subject = portalRuntimePermission.getSubject(); String servletContextName = portalRuntimePermission.getServletContextName(); String property = GetterUtil.getString( portalRuntimePermission.getProperty()); if (name.equals(PORTAL_RUNTIME_PERMISSION_EXPANDO_BRIDGE)) { if (!_expandoBridgeClassNames.contains(subject)) { logSecurityException( _log, "Attempted to get Expando bridge on " + subject); return false; } } else if (name.equals(PORTAL_RUNTIME_PERMISSION_GET_BEAN_PROPERTY)) { if (!hasGetBeanProperty( servletContextName, subject, property, permission)) { if (Validator.isNotNull(property)) { logSecurityException( _log, "Attempted to get bean property " + property + " on " + subject + " from " + servletContextName); } else { logSecurityException( _log, "Attempted to get bean property on " + subject + " from " + servletContextName); } return false; } } else if (name.startsWith(PORTAL_RUNTIME_PERMISSION_GET_CLASSLOADER)) { if (!hasGetClassLoader(subject, permission)) { logSecurityException( _log, "Attempted to get class loader " + subject); return false; } } else if (name.equals(PORTAL_RUNTIME_PERMISSION_PORTLET_BAG_POOL)) { if (!hasPortletBagPoolPortletId(subject)) { logSecurityException( _log, "Attempted to handle portlet bag pool portlet ID " + subject); return false; } } else if (name.equals(PORTAL_RUNTIME_PERMISSION_SEARCH_ENGINE)) { if (!_searchEngineIds.contains(subject)) { logSecurityException( _log, "Attempted to get search engine " + subject); return false; } } else if (name.equals(PORTAL_RUNTIME_PERMISSION_SET_BEAN_PROPERTY)) { if (!hasSetBeanProperty(servletContextName, subject, property)) { if (Validator.isNotNull(property)) { logSecurityException( _log, "Attempted to set bean property " + property + " on " + subject + " from " + servletContextName); } else { logSecurityException( _log, "Attempted to set bean property on " + subject + " from " + servletContextName); } return false; } } else if (name.equals(PORTAL_RUNTIME_PERMISSION_THREAD_POOL_EXECUTOR)) { if (!hasThreadPoolExecutorNames(subject)) { logSecurityException( _log, "Attempted to modify thread pool executor " + subject); return false; } } return true; } protected boolean hasGetBeanProperty( String contextName, String className, String property, Permission permission) { if (contextName.equals(getContextName())) { return true; } int stackIndex = Reflection.getStackIndex(6, 5); Class<?> callerClass = Reflection.getCallerClass(stackIndex); if (isTrustedCaller(callerClass, permission)) { stackIndex = stackIndex + 1; if (callerClass.equals(BeanLocatorImpl.class)) { stackIndex = stackIndex + 2; } callerClass = Reflection.getCallerClass(stackIndex); if (!callerClass.equals(TemplateContextHelper.class) && isTrustedCaller(callerClass, permission)) { return true; } } Set<String> getBeanPropertyClassNames = _getBeanPropertyClassNames.get( contextName); if (getBeanPropertyClassNames == null) { return false; } if (getBeanPropertyClassNames.contains(className)) { return true; } if (Validator.isNotNull(property)) { if (getBeanPropertyClassNames.contains( className.concat(StringPool.POUND).concat(property))) { return true; } } return false; } protected boolean hasGetClassLoader( String classLoaderReferenceId, Permission permission) { if (_classLoaderReferenceIds.contains(classLoaderReferenceId)) { return true; } int stackIndex = Reflection.getStackIndex(5, 4); Class<?> callerClass = Reflection.getCallerClass(stackIndex); String callerClassName = callerClass.getName(); if (callerClassName.equals(PortalClassLoaderUtil.class.getName()) || callerClassName.equals(PortletClassLoaderUtil.class.getName())) { callerClass = Reflection.getCallerClass(stackIndex + 1); } else if (callerClassName.equals( DynamicQueryFactoryImpl.class.getName())) { callerClass = Reflection.getCallerClass(stackIndex + 3); } if (isTrustedCaller(callerClass, permission)) { return true; } Class<?> superClass = callerClass.getSuperclass(); // Handle the case for our CLP classes if (Modifier.isAbstract(callerClass.getModifiers()) && (superClass.equals(BaseLocalServiceImpl.class) || superClass.equals(BasePersistenceImpl.class) || superClass.equals(BaseServiceImpl.class))) { return true; } return false; } protected boolean hasPortletBagPoolPortletId(String portletId) { for (Pattern portletBagPoolPortletIdPattern : _portletBagPoolPortletIdPatterns) { Matcher matcher = portletBagPoolPortletIdPattern.matcher(portletId); if (matcher.matches()) { return true; } } return false; } protected boolean hasSetBeanProperty( String contextName, String className, String property) { if (contextName.equals(getContextName())) { return true; } Set<String> setBeanPropertyClassNames = _setBeanPropertyClassNames.get( contextName); if (setBeanPropertyClassNames == null) { return false; } if (setBeanPropertyClassNames.contains(className)) { return true; } if (Validator.isNotNull(property)) { if (setBeanPropertyClassNames.contains( className.concat(StringPool.POUND).concat(property))) { return true; } } return false; } protected boolean hasThreadPoolExecutorNames( String threadPoolExecutorName) { for (Pattern threadPoolExecutorNamePattern : _threadPoolExecutorNamePatterns) { Matcher matcher = threadPoolExecutorNamePattern.matcher( threadPoolExecutorName); if (matcher.matches()) { return true; } } return false; } protected void initClassLoaderReferenceIds() { _classLoaderReferenceIds = getPropertySet( "security-manager-class-loader-reference-ids"); if (_log.isDebugEnabled()) { Set<String> classLoaderReferenceIds = new TreeSet<>( _classLoaderReferenceIds); for (String classLoaderReferenceId : classLoaderReferenceIds) { _log.debug( "Allowing access to class loader for reference " + classLoaderReferenceId); } } } protected void initExpandoBridgeClassNames() { _expandoBridgeClassNames = getPropertySet( "security-manager-expando-bridge"); if (_log.isDebugEnabled()) { Set<String> classNames = new TreeSet<>(_expandoBridgeClassNames); for (String className : classNames) { _log.debug("Allowing Expando bridge on class " + className); } } } protected void initGetBeanPropertyClassNames() { Properties properties = getProperties(); for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = (String)entry.getKey(); String value = (String)entry.getValue(); if (!key.startsWith("security-manager-get-bean-property[")) { continue; } int x = key.indexOf("["); int y = key.indexOf("]", x); String servletContextName = key.substring(x + 1, y); Set<String> getBeanPropertyClassNames = SetUtil.fromArray( StringUtil.split(value)); _getBeanPropertyClassNames.put( servletContextName, getBeanPropertyClassNames); if (_log.isDebugEnabled() && !servletContextName.equals(_PORTAL_SERVLET_CONTEXT_NAME)) { Set<String> classNames = new TreeSet<>( getBeanPropertyClassNames); for (String className : classNames) { _log.debug( "Allowing get bean property from " + servletContextName + " on class " + className); } } } // Backwards compatibility Set<String> getBeanPropertyClassNames = _getBeanPropertyClassNames.get( _PORTAL_SERVLET_CONTEXT_NAME); if (getBeanPropertyClassNames == null) { getBeanPropertyClassNames = getPropertySet( "security-manager-get-bean-property"); } else { getBeanPropertyClassNames.addAll( getPropertySet("security-manager-get-bean-property")); } _getBeanPropertyClassNames.put( _PORTAL_SERVLET_CONTEXT_NAME, getBeanPropertyClassNames); if (_log.isDebugEnabled()) { Set<String> classNames = new TreeSet<>(getBeanPropertyClassNames); for (String className : classNames) { _log.debug( "Allowing get bean property from " + _PORTAL_SERVLET_CONTEXT_NAME + " on class " + className); } } } protected void initPortletBagPoolPortletIds() { Set<String> portletBagPoolPortletIds = getPropertySet( "security-manager-portlet-bag-pool-portlet-ids"); _portletBagPoolPortletIdPatterns = new ArrayList<>( portletBagPoolPortletIds.size()); for (String portletBagPoolPortletId : portletBagPoolPortletIds) { Pattern portletBagPoolPortletIdPattern = Pattern.compile( portletBagPoolPortletId); _portletBagPoolPortletIdPatterns.add( portletBagPoolPortletIdPattern); if (_log.isDebugEnabled()) { _log.debug( "Allowing portlet bag pool portlet IDs that match the " + "regular expression " + portletBagPoolPortletId); } } } protected void initSearchEngineIds() { _searchEngineIds = getPropertySet("security-manager-search-engine-ids"); if (_log.isDebugEnabled()) { Set<String> searchEngineIds = new TreeSet<>(_searchEngineIds); for (String searchEngineId : searchEngineIds) { _log.debug("Allowing search engine " + searchEngineId); } } } protected void initSetBeanPropertyClassNames() { Properties properties = getProperties(); for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = (String)entry.getKey(); String value = (String)entry.getValue(); if (!key.startsWith("security-manager-set-bean-property[")) { continue; } int x = key.indexOf("["); int y = key.indexOf("]", x); String servletContextName = key.substring(x + 1, y); Set<String> setBeanPropertyClassNames = SetUtil.fromArray( StringUtil.split(value)); _setBeanPropertyClassNames.put( servletContextName, setBeanPropertyClassNames); if (_log.isDebugEnabled() && !servletContextName.equals(_PORTAL_SERVLET_CONTEXT_NAME)) { Set<String> classNames = new TreeSet<>( setBeanPropertyClassNames); for (String className : classNames) { _log.debug( "Allowing set bean property from " + servletContextName + " on class " + className); } } } // Backwards compatibility Set<String> setBeanPropertyClassNames = _setBeanPropertyClassNames.get( _PORTAL_SERVLET_CONTEXT_NAME); if (setBeanPropertyClassNames == null) { setBeanPropertyClassNames = getPropertySet( "security-manager-set-bean-property"); } else { setBeanPropertyClassNames.addAll( getPropertySet("security-manager-set-bean-property")); } _setBeanPropertyClassNames.put( _PORTAL_SERVLET_CONTEXT_NAME, setBeanPropertyClassNames); if (_log.isDebugEnabled()) { Set<String> classNames = new TreeSet<>(setBeanPropertyClassNames); for (String className : classNames) { _log.debug( "Allowing set bean property from " + _PORTAL_SERVLET_CONTEXT_NAME + " on class " + className); } } } protected void initThreadPoolExecutorNames() { Set<String> threadPoolExecutorNames = getPropertySet( "security-manager-thread-pool-executor-names"); _threadPoolExecutorNamePatterns = new ArrayList<>( threadPoolExecutorNames.size()); for (String threadPoolExecutorName : threadPoolExecutorNames) { Pattern threadPoolExecutorNamePattern = Pattern.compile( threadPoolExecutorName); _threadPoolExecutorNamePatterns.add(threadPoolExecutorNamePattern); if (_log.isDebugEnabled()) { _log.debug( "Allowing thread pool executors that match the regular " + "expression " + threadPoolExecutorName); } } } private static final String _PORTAL_SERVLET_CONTEXT_NAME = "portal"; private static final Log _log = LogFactoryUtil.getLog( PortalRuntimeChecker.class); private Set<String> _classLoaderReferenceIds; private Set<String> _expandoBridgeClassNames; private final Map<String, Set<String>> _getBeanPropertyClassNames = new HashMap<>(); private List<Pattern> _portletBagPoolPortletIdPatterns; private Set<String> _searchEngineIds; private final Map<String, Set<String>> _setBeanPropertyClassNames = new HashMap<>(); private List<Pattern> _threadPoolExecutorNamePatterns; }