/**
* 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.template.freemarker.internal;
import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
import com.liferay.portal.kernel.concurrent.ConcurrentHashSet;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.AggregateClassLoader;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.ClassLoaderUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.template.freemarker.configuration.FreeMarkerEngineConfiguration;
import freemarker.core.Environment;
import freemarker.core.TemplateClassResolver;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.utility.Execute;
import freemarker.template.utility.ObjectConstructor;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
/**
* @author Raymond Augé
*/
@Component(
configurationPid = "com.liferay.portal.template.freemarker.configuration.FreeMarkerEngineConfiguration",
configurationPolicy = ConfigurationPolicy.OPTIONAL, immediate = true
)
public class LiferayTemplateClassResolver implements TemplateClassResolver {
@Override
public Class<?> resolve(
String className, Environment environment, Template template)
throws TemplateException {
if (className.equals(Execute.class.getName()) ||
className.equals(ObjectConstructor.class.getName())) {
throw new TemplateException(
"Instantiating " + className + " is not allowed in the " +
"template for security reasons",
environment);
}
String[] restrictedClassNames = GetterUtil.getStringValues(
_freemarkerEngineConfiguration.restrictedClasses());
for (String restrictedClassName : restrictedClassNames) {
if (match(restrictedClassName, className)) {
throw new TemplateException(
"Instantiating " + className + " is not allowed in the " +
"template for security reasons",
environment);
}
}
boolean allowed = false;
String[] allowedClasseNames = GetterUtil.getStringValues(
_freemarkerEngineConfiguration.allowedClasses());
for (String allowedClassName : allowedClasseNames) {
if (match(allowedClassName, className)) {
allowed = true;
break;
}
}
if (allowed) {
try {
ClassLoader[] wwhitelistedClassLoaders =
_wwhitelistedClassLoaders.toArray(
new ClassLoader[_wwhitelistedClassLoaders.size()]);
ClassLoader[] classLoaders = ArrayUtil.append(
wwhitelistedClassLoaders,
ClassLoaderUtil.getContextClassLoader());
ClassLoader wwhitelistedAggregateClassLoader =
AggregateClassLoader.getAggregateClassLoader(classLoaders);
return Class.forName(
className, true, wwhitelistedAggregateClassLoader);
}
catch (Exception e) {
throw new TemplateException(e, environment);
}
}
throw new TemplateException(
"Instantiating " + className + " is not allowed in the template " +
"for security reasons",
environment);
}
@Activate
protected void activate(
BundleContext bundleContext, Map<String, Object> properties) {
_freemarkerEngineConfiguration = ConfigurableUtil.createConfigurable(
FreeMarkerEngineConfiguration.class, properties);
_classLoaderBundleTracker = new BundleTracker<>(
bundleContext, Bundle.ACTIVE,
new ClassLoaderBundleTrackerCustomizer());
_classLoaderBundleTracker.open();
_wwhitelistedClassLoaders.add(
LiferayTemplateClassResolver.class.getClassLoader());
}
@Deactivate
protected void deactivate() {
_classLoaderBundleTracker.close();
}
protected ClassLoader findClassLoader(
String clazz, BundleContext bundleContext) {
Bundle bundle = bundleContext.getBundle();
BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
List<BundleCapability> bundleCapabilities =
bundleWiring.getCapabilities(BundleRevision.PACKAGE_NAMESPACE);
for (BundleCapability bundleCapability : bundleCapabilities) {
Map<String, Object> attributes = bundleCapability.getAttributes();
String exportPackage = (String)attributes.get(
BundleRevision.PACKAGE_NAMESPACE);
if (clazz.equals(StringPool.STAR)) {
continue;
}
else if (clazz.endsWith(StringPool.STAR)) {
clazz = clazz.substring(0, clazz.length() - 1);
if (exportPackage.startsWith(clazz)) {
BundleRevision bundleRevision =
bundleCapability.getRevision();
Bundle bundleRevisionBundle = bundleRevision.getBundle();
BundleWiring bundleRevisionBundleWiring =
bundleRevisionBundle.adapt(BundleWiring.class);
return bundleRevisionBundleWiring.getClassLoader();
}
}
else if (clazz.equals(exportPackage)) {
BundleRevision bundleRevision = bundleCapability.getRevision();
Bundle bundleRevisionBundle = bundleRevision.getBundle();
BundleWiring bundleRevisionBundleWiring =
bundleRevisionBundle.adapt(BundleWiring.class);
return bundleRevisionBundleWiring.getClassLoader();
}
else {
String allowedClassPackage = clazz.substring(
0, clazz.lastIndexOf("."));
if (allowedClassPackage.equals(exportPackage)) {
BundleRevision bundleRevision =
bundleCapability.getRevision();
Bundle bundleRevisionBundle = bundleRevision.getBundle();
BundleWiring bundleRevisionBundleWiring =
bundleRevisionBundle.adapt(BundleWiring.class);
return bundleRevisionBundleWiring.getClassLoader();
}
}
}
return null;
}
protected ClassLoader findClassLoader(
String[] allowedClassNames, BundleContext bundleContext) {
if (allowedClassNames == null) {
allowedClassNames = new String[0];
}
for (String allowedClassName : allowedClassNames) {
if (Validator.isBlank(allowedClassName)) {
continue;
}
ClassLoader classLoader = findClassLoader(
allowedClassName, bundleContext);
if (classLoader != null) {
return classLoader;
}
if (_log.isWarnEnabled()) {
Bundle bundle = bundleContext.getBundle();
_log.warn(
"Bundle " + bundle.getSymbolicName() + " does not export " +
allowedClassName);
}
}
return null;
}
protected boolean match(String className, String matchedClassName) {
if (className.equals(StringPool.STAR)) {
return true;
}
else if (className.endsWith(StringPool.STAR)) {
className = className.substring(0, className.length() - 1);
if (matchedClassName.startsWith(className)) {
return true;
}
}
else if (className.equals(matchedClassName)) {
return true;
}
else {
String packageName = matchedClassName.substring(
0, matchedClassName.lastIndexOf("."));
if (packageName.equals(className)) {
return true;
}
}
return false;
}
@Modified
protected void modified(
BundleContext bundleContext, Map<String, Object> properties) {
_freemarkerEngineConfiguration = ConfigurableUtil.createConfigurable(
FreeMarkerEngineConfiguration.class, properties);
for (Bundle bundle : _bundles) {
ClassLoader classLoader = findClassLoader(
_freemarkerEngineConfiguration.allowedClasses(),
bundle.getBundleContext());
if (classLoader != null) {
_wwhitelistedClassLoaders.add(classLoader);
}
}
}
private static final Log _log = LogFactoryUtil.getLog(
LiferayTemplateClassResolver.class);
private final Set<Bundle> _bundles = new ConcurrentHashSet<>();
private BundleTracker<ClassLoader> _classLoaderBundleTracker;
private volatile FreeMarkerEngineConfiguration
_freemarkerEngineConfiguration;
private final Set<ClassLoader> _wwhitelistedClassLoaders =
new ConcurrentHashSet<>();
private class ClassLoaderBundleTrackerCustomizer
implements BundleTrackerCustomizer<ClassLoader> {
@Override
public ClassLoader addingBundle(
Bundle bundle, BundleEvent bundleEvent) {
ClassLoader classLoader = findClassLoader(
_freemarkerEngineConfiguration.allowedClasses(),
bundle.getBundleContext());
if (classLoader != null) {
_wwhitelistedClassLoaders.add(classLoader);
}
_bundles.add(bundle);
BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
return bundleWiring.getClassLoader();
}
@Override
public void modifiedBundle(
Bundle bundle, BundleEvent bundleEvent, ClassLoader classLoader) {
}
@Override
public void removedBundle(
Bundle bundle, BundleEvent bundleEvent, ClassLoader classLoader) {
_wwhitelistedClassLoaders.remove(classLoader);
_bundles.remove(bundle);
}
}
}