diff --git a/grouper-misc/grouper-test-plugin/pom.xml b/grouper-misc/grouper-test-plugin/pom.xml new file mode 100644 index 000000000000..15eb0617c3f5 --- /dev/null +++ b/grouper-misc/grouper-test-plugin/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + + edu.internet2.middleware.grouper + grouper-parent + 5.0.0-SNAPSHOT + ../../grouper-parent + + + Grouper Test plugin + A Grouper test plugin. This can also be used as a protype/sample for creating a plugin + grouper-test-plugin + bundle + + + + org.osgi + osgi.core + provided + + + javax + javaee-web-api + 8.0.1 + provided + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + true + + ${project.name} + ${project.organization.name} + ${project.artifactId} + ${project.version} + ${project.url} + ${maven.build.timestamp} + + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.groupId}.${project.artifactId} + ${project.artifactId}-bundle + ${project.version} + edu.internet2.middleware.grouper.plugin.test.TestPlugin + edu.internet2.middleware.grouper.plugin.test.filter + edu.internet2.middleware.grouper.plugin.* + *;scope=compile|runtime;inline=false + false + + !* + + + + + + org.apache.maven.plugins + maven-jarsigner-plugin + 3.0.0 + + + sign + + sign + + + + + ${project.basedir}/src/main/signing/signing.jks + signing + signing + signing + + + + + \ No newline at end of file diff --git a/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/TestPlugin.java b/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/TestPlugin.java new file mode 100644 index 000000000000..7cf14befcff1 --- /dev/null +++ b/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/TestPlugin.java @@ -0,0 +1,26 @@ +package edu.internet2.middleware.grouper.plugin.test; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +import javax.servlet.ServletContainerInitializer; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +public class TestPlugin implements BundleActivator { + private Map registrationMap = new HashMap<>(); + + @Override + public void start(BundleContext bundleContext) throws Exception { + TestPluginServletContainerInitializer initializer = new TestPluginServletContainerInitializer(); + ServiceRegistration registration = bundleContext.registerService(ServletContainerInitializer.class, initializer, new Hashtable<>()); + registrationMap.put(TestPluginServletContainerInitializer.class.getCanonicalName(), registration); + } + + @Override + public void stop(BundleContext bundleContext) throws Exception { + registrationMap.values().forEach(x -> { x.unregister(); } ); + } +} diff --git a/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/TestPluginServletContainerInitializer.java b/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/TestPluginServletContainerInitializer.java new file mode 100644 index 000000000000..f3b15000d3c7 --- /dev/null +++ b/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/TestPluginServletContainerInitializer.java @@ -0,0 +1,18 @@ +package edu.internet2.middleware.grouper.plugin.test; + +import edu.internet2.middleware.grouper.plugin.test.filter.TestFilter; + +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import java.util.Set; + +public class TestPluginServletContainerInitializer implements ServletContainerInitializer { + @Override + public void onStartup(Set> set, ServletContext servletContext) throws ServletException { + TestFilter testFilter = new TestFilter(); + FilterRegistration.Dynamic testFilterRegistration = servletContext.addFilter("testFilter", testFilter); + testFilterRegistration.addMappingForUrlPatterns(null, false, "/*"); + } +} diff --git a/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/filter/TestFilter.java b/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/filter/TestFilter.java new file mode 100644 index 000000000000..aec006c65236 --- /dev/null +++ b/grouper-misc/grouper-test-plugin/src/main/java/edu/internet2/middleware/grouper/plugin/test/filter/TestFilter.java @@ -0,0 +1,15 @@ +package edu.internet2.middleware.grouper.plugin.test.filter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; + +public class TestFilter implements Filter { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + filterChain.doFilter(servletRequest, servletResponse); + } +} diff --git a/grouper-misc/grouper-test-plugin/src/main/signing/README.md b/grouper-misc/grouper-test-plugin/src/main/signing/README.md new file mode 100644 index 000000000000..ad9420d743c7 --- /dev/null +++ b/grouper-misc/grouper-test-plugin/src/main/signing/README.md @@ -0,0 +1 @@ +These files are strictly examples and for testing. Do not use them in your own projects! \ No newline at end of file diff --git a/grouper-misc/grouper-test-plugin/src/main/signing/signing.crt b/grouper-misc/grouper-test-plugin/src/main/signing/signing.crt new file mode 100644 index 000000000000..1c6272ecf5ac --- /dev/null +++ b/grouper-misc/grouper-test-plugin/src/main/signing/signing.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgIILehkAIY5xvQwDQYJKoZIhvcNAQELBQAwaDEQMA4GA1UE +BhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQ +MA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEMMAoGA1UEAwwDSmoh +MB4XDTIzMTAxMjAwMjE1OFoXDTMzMTAxMTAwMjE1OFowaDEQMA4GA1UEBhMHVW5r +bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE +ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEMMAoGA1UEAwwDSmohMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Y8HkztTbh1gXW+lIB175CYiqR5U +qPVaCe9efvLd89zzyKpO1wxdp7lbJwwHfepuFhsGPMl38aD6YXeQDRKj3m7u2m94 +I1PAiAYslRSN/3oWyv7RQwP3gn4XAegpR4kBCNJoXLTPK80oyYA62okVNtCH/dFq +Z3k3A1O+OoeqcDBq5KzuPatViFAjEyyLCcV1g0mLQlCfzrWFw5OVp2U6q2D0qLKF +5i2bFkIlwxcWY0tfvv0S8Lp7+JpPN/etXGByKigM4Cu9CnfLBUs0v9h4wH5wv/Zj +/XGfViD+pemIgkR82BqPM31TeWU0ONOYapS0LS4RpZBOcLtXcwgBMIch2QIDAQAB +oyEwHzAdBgNVHQ4EFgQU5cbJ7MBtBDej6XYd3iukheyNO5kwDQYJKoZIhvcNAQEL +BQADggEBAMjtgjBespxleA5Po49A3kng/A1n24m0SuGDVRmtWm5OGi34e+A64GIo +kgiux+r0HOSDHUaIfao1L84FIxIBGGSHW/hcZ8RJr1qSYvW7KnD9PfgAcghtokKx +38fx3Q3RUDB6Gcz2vigTkJ7OzR1wkyzVvfnLEOL3kA1P49Eb/wyMKEmmLDQyij6n +JG+T6+M4+QKiA8W9yIIAAjJFoNL0LBczsdVn6o34buwqLPhjhVoGazDbax5cXxkJ +8TRdktqQNbBqDk+6mOto8MCJUtg7d+Run+HvtgjNsdYLI6J+6K9+51STLtXST90V +voa3jXptx9Tl/BFoJK2sJWdoIwG3zug= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/grouper-misc/grouper-test-plugin/src/main/signing/signing.jks b/grouper-misc/grouper-test-plugin/src/main/signing/signing.jks new file mode 100644 index 000000000000..27e0a33e4752 Binary files /dev/null and b/grouper-misc/grouper-test-plugin/src/main/signing/signing.jks differ diff --git a/grouper-misc/grouper-test-plugin/src/main/signing/test.jks b/grouper-misc/grouper-test-plugin/src/main/signing/test.jks new file mode 100644 index 000000000000..d6cc98835617 Binary files /dev/null and b/grouper-misc/grouper-test-plugin/src/main/signing/test.jks differ diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.properties b/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.properties deleted file mode 100644 index 2fa273ea373b..000000000000 --- a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.properties +++ /dev/null @@ -1,35 +0,0 @@ -# directory of plugins, default to /opt/grouper/grouperWebapp/WEB-INF/grouperPlugins -# {valueType: "string", required: true, order: 1000} -grouper.osgi.jar.dir = C:/Users/jj-unicon/workspace/grouper.se/grouper/grouper-misc/grouper-authentication/target - -# directory of felix cache of plugins, default to /opt/grouper/grouperWebapp/WEB-INF/grouperFelixCache -# {valueType: "string", required: true, order: 2000} -grouper.felix.cache.rootdir = c:/tmp/grouperFelixCache - -#grouperOsgiPlugin.extAuthPlugin.jarName = grouper-authentication-plugin-0.0.1-SNAPSHOT.jar -#grouperOsgiPlugin.extAuthPlugin.numberOfImplementations = 2 -#grouperOsgiPlugin.extAuthPlugin.osgiImplementation.0.implementationClass = edu.internet2.middleware.grouper.authentication.plugin.filter.SecurityFilterDecorator -#grouperOsgiPlugin.extAuthPlugin.osgiImplementation.0.implementsInterface = javax.servlet.Filter -#grouperOsgiPlugin.extAuthPlugin.osgiImplementation.1.implementationClass = edu.internet2.middleware.grouper.authentication.plugin.filter.CallbackFilterDecorator -#grouperOsgiPlugin.extAuthPlugin.osgiImplementation.1.implementsInterface = javax.servlet.Filter - -############################################ -## External Authentication plugin -############################################ -# Enable external authorization security filters -# {valueType: "boolean", defaultValue: "false"} -grouper.is.extAuth.enabled=true - -# Name of the jar containing the external authorization plugin -# {valueType: "string", required: true} -grouper.extAuth.jarname=grouper-authentication-0.0.1-SNAPSHOT.jar - -# Callback filter implementation classname -# ex: edu.internet2.middleware.grouper.plugins.testImplementation.SamplePluginProviderServiceImpl -# ${valueType: "class", required: true} -grouper.extAuth.filter.callback.implmentation.className=edu.internet2.middleware.grouper.authentication.plugin.filter.CallbackFilterDecorator - -# Security filter implementation classname -# ex: edu.internet2.middleware.grouper.plugins.testImplementation.SamplePluginProviderServiceImpl -# ${valueType: "class", required: true} -grouper.extAuth.filter.security.implmentation.className=edu.internet2.middleware.grouper.authentication.plugin.filter.SecurityFilterDecorator diff --git a/grouper-misc/webapp/grouper-ui-webapp/pom.xml b/grouper-misc/webapp/pom.xml similarity index 54% rename from grouper-misc/webapp/grouper-ui-webapp/pom.xml rename to grouper-misc/webapp/pom.xml index 8a8ec64fcbc1..ed2b984fde97 100644 --- a/grouper-misc/webapp/grouper-ui-webapp/pom.xml +++ b/grouper-misc/webapp/pom.xml @@ -25,11 +25,11 @@ edu.internet2.middleware.grouper grouper-parent 5.0.0-SNAPSHOT - ../../../grouper-parent + ../../grouper-parent - - Grouper UI webapp - grouper-ui-webapp + + Grouper webapp + grouper-webapp war @@ -38,12 +38,17 @@ grouper-ui ${project.version} + + ${project.groupId} + grouper-ws + ${project.version} + - ../../../grouper/conf + ../../grouper/conf @@ -55,19 +60,21 @@ false - ../../../grouper-ui/webapp + ../../grouper-ui/webapp - org.eclipse.jetty - jetty-maven-plugin - 9.4.27.v20200227 + org.codehaus.cargo + cargo-maven3-plugin + 1.10.9 - - /grouper - + + + 8080 + + @@ -84,5 +91,33 @@ + + debug + + + + org.codehaus.cargo + cargo-maven3-plugin + 1.10.9 + + + 3600000 + + + + 8080 + + -Xdebug + -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005 + -Xnoagent + -Djava.compiler=NONE + + + + + + + + diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper-loader.properties b/grouper-misc/webapp/src/test/resources/grouper-loader.properties similarity index 100% rename from grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper-loader.properties rename to grouper-misc/webapp/src/test/resources/grouper-loader.properties diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper-ui.properties b/grouper-misc/webapp/src/test/resources/grouper-ui.properties similarity index 100% rename from grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper-ui.properties rename to grouper-misc/webapp/src/test/resources/grouper-ui.properties diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.cache.properties b/grouper-misc/webapp/src/test/resources/grouper.cache.properties similarity index 100% rename from grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.cache.properties rename to grouper-misc/webapp/src/test/resources/grouper.cache.properties diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.client.properties b/grouper-misc/webapp/src/test/resources/grouper.client.properties similarity index 100% rename from grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.client.properties rename to grouper-misc/webapp/src/test/resources/grouper.client.properties diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.hibernate.properties b/grouper-misc/webapp/src/test/resources/grouper.hibernate.properties similarity index 100% rename from grouper-misc/webapp/grouper-ui-webapp/src/test/resources/grouper.hibernate.properties rename to grouper-misc/webapp/src/test/resources/grouper.hibernate.properties diff --git a/grouper-misc/webapp/src/test/resources/grouper.properties b/grouper-misc/webapp/src/test/resources/grouper.properties new file mode 100644 index 000000000000..6ce84329634c --- /dev/null +++ b/grouper-misc/webapp/src/test/resources/grouper.properties @@ -0,0 +1,15 @@ +grouper.osgi.enable = true +grouper.osgi.security.enable=false + +# directory of plugins, default to /opt/grouper/grouperWebapp/WEB-INF/grouperPlugins +# {valueType: "string", required: true, order: 1000} +grouper.osgi.jar.dir = /Users/jj/Documents/workspace/community/grouper-ext-auth/target + +# directory of felix cache of plugins, default to /opt/grouper/grouperWebapp/WEB-INF/grouperFelixCache +# {valueType: "string", required: true, order: 2000} +grouper.felix.cache.rootdir = /tmp/grouperFelixCache + +grouper.osgi.plugin.extauth.location=file:/Users/jj/Documents/workspace/community/grouper-ext-auth/target/grouper-authentication-plugin-0.0.1-SNAPSHOT.jar + +# grouper.osgi.framework.system.packages.extra=javax.*,org.osgi.*,org.osgi,org.apache.commons.logging,edu.internet2.middleware.grouperClient.config,edu.internet2.middleware.grouper.app.externalSystem,org.w3c.dom.* +# grouper.osgi.framework.boot.delegation=javax.*,org.osgi.*,org.osgi,org.apache.commons.logging,edu.internet2.middleware.grouperClient.config,edu.internet2.middleware.grouper.app.externalSystem,org.w3c.dom.* \ No newline at end of file diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/morphString.properties b/grouper-misc/webapp/src/test/resources/morphString.properties similarity index 100% rename from grouper-misc/webapp/grouper-ui-webapp/src/test/resources/morphString.properties rename to grouper-misc/webapp/src/test/resources/morphString.properties diff --git a/grouper-misc/webapp/grouper-ui-webapp/src/test/resources/subject.properties b/grouper-misc/webapp/src/test/resources/subject.properties similarity index 100% rename from grouper-misc/webapp/grouper-ui-webapp/src/test/resources/subject.properties rename to grouper-misc/webapp/src/test/resources/subject.properties diff --git a/grouper-parent/pom.xml b/grouper-parent/pom.xml index b16d60d3593d..d2951eadf4bc 100644 --- a/grouper-parent/pom.xml +++ b/grouper-parent/pom.xml @@ -37,7 +37,8 @@ ../grouper-ui ../grouper-ws ../grouper-misc/grouper-installer - ../grouper-misc/webapp/grouper-ui-webapp + ../grouper-misc/webapp + ../grouper-misc/grouper-test-plugin @@ -95,7 +96,7 @@ 0.9.5 0.2.15 3.3.0 - 7.0.3 + 7.0.5 2.81 1.4.20 0.1.55 @@ -195,6 +196,9 @@ 2.0.8 1.4 + + + 8.0.0 @@ -343,11 +347,6 @@ cglib ${cglib.version} - - org.apache.felix - org.apache.felix.framework - ${felix.version} - com.thoughtworks.xstream xstream @@ -965,6 +964,18 @@ ${amqp-client.version} + + + org.osgi + osgi.core + ${osgi.version} + + + org.apache.felix + org.apache.felix.framework + ${felix.version} + + diff --git a/grouper-ui/webapp/index.jsp b/grouper-ui/webapp/index.jsp index 88a71bf4ef57..40a5a981526c 100644 --- a/grouper-ui/webapp/index.jsp +++ b/grouper-ui/webapp/index.jsp @@ -2,7 +2,6 @@ <%@ include file="WEB-INF/grouperUi2/assetsJsp/commonTaglib.jsp"%> <% - GrouperRequestContainer.retrieveFromRequestOrCreate(); String location="grouperUi/app/UiV2Main.index?operation=UiV2Main.indexMain"; %> diff --git a/grouper/pom.xml b/grouper/pom.xml index c88404a1a901..0776740a2a90 100644 --- a/grouper/pom.xml +++ b/grouper/pom.xml @@ -96,10 +96,19 @@ cglib cglib + + org.apache.felix org.apache.felix.framework + + edu.internet2.middleware.grouper.plugins + felix-framework-security + 0.0.1 + test + + com.amazonaws aws-java-sdk-core @@ -568,6 +577,9 @@ src/test + + src/main/resources + conf @@ -591,6 +603,8 @@ **/*.jexl edu/internet2/middleware/grouper/xml/export.properties edu/internet2/middleware/grouper/xml/import.properties + edu/internet2/middleware/grouper/plugins/all.policy + edu/internet2/middleware/grouper/plugins/org.apache.felix.framework.security-2.8.4.jar @@ -607,6 +621,9 @@ + + + plugins/**/* diff --git a/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/BundleStarter.java b/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/BundleStarter.java index ee77b9ab43e7..e20863ddc331 100644 --- a/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/BundleStarter.java +++ b/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/BundleStarter.java @@ -1,8 +1,10 @@ package edu.internet2.middleware.grouper.plugins; +import edu.internet2.middleware.grouper.Group; import edu.internet2.middleware.grouper.cfg.GrouperConfig; import edu.internet2.middleware.grouper.util.GrouperUtil; import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; @@ -18,16 +20,21 @@ public class BundleStarter { private static final Log LOG = GrouperUtil.getLog(BundleStarter.class); + + public static final String GROUPER_OSGI_EXCEPTION_ON_PLUGIN_LOAD_ERROR = "grouper.osgi.exceptionOnPluginLoadError"; private final BundleContext bundleContext; private final String bundleDirWithLastSlash; + private final Map installedBundles; + public BundleStarter(BundleContext bundleContext) { this.bundleContext = bundleContext; + this.installedBundles = new HashMap<>(); try { Path tmpDir = Files.createTempDirectory("grouper"); - bundleDirWithLastSlash = GrouperUtil.stripLastSlashIfExists(GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.jar.dir", tmpDir.toString())) + File.separator; + bundleDirWithLastSlash = GrouperUtil.stripLastSlashIfExists(GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.jar.dir", tmpDir.toString())) + "/"; } catch (IOException e) { throw new RuntimeException(e); } @@ -36,16 +43,48 @@ public BundleStarter(BundleContext bundleContext) { public void start() { Map, Set>> pluginJarNameToInterfaceToImplementationClasses = new HashMap, Set>>(); - Pattern pattern = Pattern.compile("^grouperOsgiPlugin\\.([^.]+)\\.jarName$"); + if (!GrouperConfig.retrieveConfig().containsKey(GROUPER_OSGI_EXCEPTION_ON_PLUGIN_LOAD_ERROR)) { + LOG.warn("DEPRECATION WARNING: You are currently using the default behavior for error handling on plugin load (log and continue). This behavior will change in the future. If you'd want to use future behavior, set `" + GROUPER_OSGI_EXCEPTION_ON_PLUGIN_LOAD_ERROR + "=true` in `grouper.properties`"); + } + boolean exceptionOnLoad = GrouperConfig.retrieveConfig().propertyValueBoolean(GROUPER_OSGI_EXCEPTION_ON_PLUGIN_LOAD_ERROR, false); + + // TODO: deprecate + { + Pattern pattern = Pattern.compile("^grouperOsgiPlugin\\.([^.]+)\\.jarName$"); + Set configIds = GrouperConfig.retrieveConfig().propertyConfigIds(pattern); + if (GrouperUtil.length(configIds) > 0) { + for (String configId: configIds) { + LOG.warn("DEPRECATION WARNING: you are using older configuration in the namespace `grouperOsgiPlugin`; this will be removed in future versions. Update configuration to use `grouper.osgi.plugin`"); + String jarName = GrouperConfig.retrieveConfig().propertyValueString("grouperOsgiPlugin." + configId + ".jarName"); + Bundle bundle = null; + try { + bundle = bundleContext.installBundle("file:" + bundleDirWithLastSlash + jarName); + bundle.start(); + installedBundles.put(configId, bundle); + } catch (BundleException e) { + if (exceptionOnLoad) { + throw new GrouperPluginException("Problem installing plugin: " + jarName, e); + } + LOG.error("Problem installing plugin: " + jarName, e); + } + } + } + } + + Pattern pattern = Pattern.compile("^grouper\\.osgi\\.plugin\\.([^.]+)\\.location$"); Set configIds = GrouperConfig.retrieveConfig().propertyConfigIds(pattern); if (GrouperUtil.length(configIds) > 0) { for (String configId: configIds) { - String jarName = GrouperConfig.retrieveConfig().propertyValueString("grouperOsgiPlugin." + configId + ".jarName"); + String location = GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.plugin." + configId + ".location"); try { - Bundle bundle = bundleContext.installBundle("file:" + bundleDirWithLastSlash + jarName); + Bundle bundle = bundleContext.installBundle(location); bundle.start(); + installedBundles.put(configId, bundle); } catch (BundleException e) { - LOG.error("Problem installing plugin: " + jarName, e); + if (exceptionOnLoad) { + throw new GrouperPluginException("Problem installing plugin: " + location, e); + } + LOG.error("Problem installing plugin: " + location, e); } } } diff --git a/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/FrameworkStarter.java b/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/FrameworkStarter.java index 7d5213b60d77..af940fe6c5d0 100644 --- a/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/FrameworkStarter.java +++ b/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/FrameworkStarter.java @@ -1,9 +1,11 @@ package edu.internet2.middleware.grouper.plugins; -import edu.internet2.middleware.grouper.app.externalSystem.GrouperExternalSystem; import edu.internet2.middleware.grouper.cfg.GrouperConfig; import edu.internet2.middleware.grouper.cfg.GrouperHibernateConfig; +import edu.internet2.middleware.grouper.util.GrouperUtil; import edu.internet2.middleware.grouperClient.config.ConfigPropertiesCascadeBase; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; @@ -12,10 +14,20 @@ import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Arrays; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; @@ -23,6 +35,7 @@ import java.util.Map; import java.util.ServiceLoader; import java.util.Set; +import java.util.regex.Pattern; /** * Class used to start up the OSGI framework @@ -30,8 +43,18 @@ * @author jj */ public class FrameworkStarter { + private final static Log LOG = GrouperUtil.getLog(FrameworkStarter.class); + private final static FrameworkStarter frameworkStarter = new FrameworkStarter(); + // properties + public final static String GROUPER_OSGI_ENABLE = "grouper.osgi.enable"; + public final static String GROUPER_OSGI_SECURITY_ENABLE = "grouper.osgi.security.enable"; + public final static String GROUPER_OSGI_FRAMEWORK_TRUST_REPOSITORIES = "grouper.osgi.framework.trust.repositories"; + public final static String GROUPER_OSGI_FRAMEWORK_SYSTEM_PACKAGES_EXTRA = "grouper.osgi.framework.system.packages.extra"; + public final static String GROUPER_OSGI_CACHE_ROOTDIR = "grouper.osgi.cache.rootdir"; + public final static String GROUPER_OSGI_FRAMEWORK_BOOT_DELEGATION = "grouper.osgi.framework.boot.delegation"; + private Framework framework; private FrameworkStarter(){} @@ -41,23 +64,35 @@ public static FrameworkStarter getInstance() { } public void start() { - if (!GrouperConfig.retrieveConfig().propertyValueBoolean("grouper.osgi.enable", false)) { + if (!GrouperConfig.retrieveConfig().propertyValueBoolean(GROUPER_OSGI_ENABLE, false)) { return; } Map configMap = new HashMap<>(); + // setup osgi security if enabled + if (GrouperConfig.retrieveConfig().propertyValueBoolean(GROUPER_OSGI_SECURITY_ENABLE, false)) { + setupSecurity(configMap); + } + configMap.put(Constants.FRAMEWORK_BUNDLE_PARENT, Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK); // if it caches modules, they might not ever get reloaded configMap.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT); - //TODO: maybe make this more dynamic. currently we're very opinionated on what we export - configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.framework.system.packages.extra","javax.servlet,javax.servlet.http")); + Set frameworkSystemPackagesExtra = new HashSet<>(); + // TODO: add any needed system packages here + if (null != GrouperConfig.retrieveConfig().propertyValueString(GROUPER_OSGI_FRAMEWORK_SYSTEM_PACKAGES_EXTRA)) { + LOG.warn("You are setting a value for `grouper.osgi.framework.system.packages.extra`. This generally is not needed and should not be used unless there is a good reason to do so"); + frameworkSystemPackagesExtra.addAll(Arrays.asList(GrouperConfig.retrieveConfig().propertyValueString(GROUPER_OSGI_FRAMEWORK_SYSTEM_PACKAGES_EXTRA, "").split(","))); + } + if (!frameworkSystemPackagesExtra.isEmpty()) { + configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, String.join(",", frameworkSystemPackagesExtra)); + } try { // set up cachedir Path cacheDir = Files.createTempDirectory("osgi-cache"); - String grouperOsgiCacheDir = GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.cache.rootdir", cacheDir.toString()); + String grouperOsgiCacheDir = GrouperConfig.retrieveConfig().propertyValueString(GROUPER_OSGI_CACHE_ROOTDIR, cacheDir.toString()); configMap.put(Constants.FRAMEWORK_STORAGE, grouperOsgiCacheDir); } catch (IOException e) { throw new RuntimeException("problem with setting up osgi cache directory", e); @@ -65,18 +100,18 @@ public void start() { // usually, this is a bad idea, but we have several classes that must be loaded from the framework classpath to work, // e.g., logging, configuration - String packagesForBootDelegationString; - if (null != GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.framework.boot.delegation")) { - packagesForBootDelegationString = GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.framework.boot.delegation"); + Set packagesForBootDelegation = new HashSet<>(); + if (null != GrouperConfig.retrieveConfig().propertyValueString(GROUPER_OSGI_FRAMEWORK_BOOT_DELEGATION)) { + LOG.warn("You are setting a value for `grouper.osgi.framework.boot.delegation`. This is generally not needed adn should not be used unless there is a good reason to do so"); + packagesForBootDelegation.addAll(Arrays.asList(GrouperConfig.retrieveConfig().propertyValueString(GROUPER_OSGI_FRAMEWORK_BOOT_DELEGATION).split(","))); } else { - Set packagesForBootDelegation = new HashSet<>(); - packagesForBootDelegation.add(LogFactory.class.getPackage().getName()); - packagesForBootDelegation.add(ConfigPropertiesCascadeBase.class.getPackage().getName()); - // TODO: why oh why... need to fix this - packagesForBootDelegation.add(GrouperExternalSystem.class.getPackage().getName()); - packagesForBootDelegationString = String.join(",", packagesForBootDelegation); + packagesForBootDelegation.add("org.osgi.*"); + packagesForBootDelegation.add("javax.*"); + packagesForBootDelegation.add("org.apache.commons.logging"); + packagesForBootDelegation.add("edu.internet2.middleware.grouper.*"); + packagesForBootDelegation.add("edu.internet2.middleware.grouperClient.*"); } - configMap.put(Constants.FRAMEWORK_BOOTDELEGATION, packagesForBootDelegationString); + configMap.put(Constants.FRAMEWORK_BOOTDELEGATION, String.join(",", packagesForBootDelegation)); try { FrameworkFactory frameworkFactory = ServiceLoader.load(FrameworkFactory.class).iterator().next(); @@ -85,6 +120,12 @@ public void start() { this.registerConfigurationServices(); + // install security plugin, if needed + // TODO: currently, we have a custom version of the plugin that fixes a problem with allowing plugins that are signed with untrusted certificates. this should change + if (GrouperConfig.retrieveConfig().propertyValueBoolean(GROUPER_OSGI_SECURITY_ENABLE, false)) { + framework.getBundleContext().installBundle(FrameworkStarter.class.getResource("/plugins/org.apache.felix.framework.security.jar").toString()).start(); + } + BundleStarter bundleStarter = new BundleStarter(framework.getBundleContext()); bundleStarter.start(); } catch (BundleException e) { @@ -92,6 +133,43 @@ public void start() { } } + // setup up osgi security cofiguration. This includes setting up a jvm security policy and setting up a truststore + private static void setupSecurity(Map configMap) { + // TODO: this seems very dangerous, but is also going to be deprecated. look into options, though for now there should be a bit of manual trust as a buffer + System.setProperty("java.security.policy", FrameworkStarter.class.getResource("/plugins/all.policy").toString()); + + configMap.put(Constants.FRAMEWORK_SECURITY, Constants.FRAMEWORK_SECURITY_OSGI); + + // setup trust repositories + // TODO: look into making this more dynamic. Currently, according to spec, the only required way of setting the trust stores is to set a property with java.io.File.pathSeparator (`:` on unix, `;` on windows) separated list of keystores, with each implementation having the option to providing other mechanisms + Pattern pattern = Pattern.compile("^grouper\\.osgi\\.truststore\\.([^.]+)\\.certificate$"); + Set trustCertificatesAliases = GrouperConfig.retrieveConfig().propertyConfigIds(pattern); + if (GrouperConfig.retrieveConfig().propertyValueString(GROUPER_OSGI_FRAMEWORK_TRUST_REPOSITORIES) != null) { + configMap.put(Constants.FRAMEWORK_TRUST_REPOSITORIES, GrouperConfig.retrieveConfig().propertyValueString(GROUPER_OSGI_FRAMEWORK_TRUST_REPOSITORIES)); + } else if (!trustCertificatesAliases.isEmpty()) { + // we have certificates defined in configuration + try { + KeyStore keyStore = KeyStore.getInstance("JKS"); + String keyStorePassword = "changeme"; + keyStore.load(null, keyStorePassword.toCharArray()); + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + for (String alias: trustCertificatesAliases) { + Certificate certificate = certificateFactory.generateCertificate(new ReaderInputStream(new StringReader(GrouperConfig.retrieveConfig().propertyValueString("grouper.osgi.truststore." + alias + ".certificate")))); + keyStore.setCertificateEntry(alias, certificate); + } + + Path keystorePath = Files.createTempFile("keystore", ".jks"); + try (OutputStream os = new FileOutputStream(keystorePath.toFile())) { + keyStore.store(os, keyStorePassword.toCharArray()); + configMap.put(Constants.FRAMEWORK_TRUST_REPOSITORIES, keystorePath.toString()); + } + } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + } + /** * register various Grouper services */ @@ -125,6 +203,13 @@ private static Dictionary buildSimpleDictionary(String key, String value) { public Framework getFramework() { return this.framework; } + + public synchronized void stop() throws BundleException { + if (this.framework != null) { + this.framework.stop(); + this.framework = null; + } + } // public void stop() { // if (!started) { diff --git a/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/GrouperPluginException.java b/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/GrouperPluginException.java new file mode 100644 index 000000000000..17db5b6a880f --- /dev/null +++ b/grouper/src/grouper/edu/internet2/middleware/grouper/plugins/GrouperPluginException.java @@ -0,0 +1,22 @@ +package edu.internet2.middleware.grouper.plugins; + +public class GrouperPluginException extends RuntimeException{ + public GrouperPluginException() { + } + + public GrouperPluginException(String message) { + super(message); + } + + public GrouperPluginException(String message, Throwable cause) { + super(message, cause); + } + + public GrouperPluginException(Throwable cause) { + super(cause); + } + + public GrouperPluginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/grouper/src/main/resources/plugins/all.policy b/grouper/src/main/resources/plugins/all.policy new file mode 100644 index 000000000000..eb7803737747 --- /dev/null +++ b/grouper/src/main/resources/plugins/all.policy @@ -0,0 +1,3 @@ +grant { + permission java.security.AllPermission; +}; \ No newline at end of file diff --git a/grouper/src/main/resources/plugins/org.apache.felix.framework.security.jar b/grouper/src/main/resources/plugins/org.apache.felix.framework.security.jar new file mode 100644 index 000000000000..67fbc256bd75 Binary files /dev/null and b/grouper/src/main/resources/plugins/org.apache.felix.framework.security.jar differ diff --git a/grouper/src/test/edu/internet2/middleware/grouper/plugins/FrameworkStarterTests.java b/grouper/src/test/edu/internet2/middleware/grouper/plugins/FrameworkStarterTests.java new file mode 100644 index 000000000000..a03a28bf96ec --- /dev/null +++ b/grouper/src/test/edu/internet2/middleware/grouper/plugins/FrameworkStarterTests.java @@ -0,0 +1,84 @@ +package edu.internet2.middleware.grouper.plugins; + +import edu.internet2.middleware.grouper.cfg.GrouperConfig; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.BundleException; + +import java.io.IOException; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class FrameworkStarterTests { + @Before + public void setup() { + GrouperConfig.retrieveConfig().propertiesOverrideMap().clear(); + + // common properties + GrouperConfig.retrieveConfig().propertiesOverrideMap().put(FrameworkStarter.GROUPER_OSGI_ENABLE, "true"); + GrouperConfig.retrieveConfig().propertiesOverrideMap().put(FrameworkStarter.GROUPER_OSGI_SECURITY_ENABLE, "true"); + GrouperConfig.retrieveConfig().propertiesOverrideMap().put(BundleStarter.GROUPER_OSGI_EXCEPTION_ON_PLUGIN_LOAD_ERROR, "true"); + } + + @After + public void tearDown() throws BundleException { + FrameworkStarter.getInstance().stop(); + } + + @Test + public void testSecurityBasic() { + GrouperConfig.retrieveConfig().propertiesOverrideMap().put(FrameworkStarter.GROUPER_OSGI_FRAMEWORK_TRUST_REPOSITORIES, FrameworkStarterTests.class.getResource("/plugins/test.jks").getPath()); + GrouperConfig.retrieveConfig().propertiesOverrideMap().put("grouper.osgi.plugin.test.location", FrameworkStarterTests.class.getResource("/plugins/grouper-test-plugin.jar").toString()); + + FrameworkStarter.getInstance().start(); + } + + @Test + public void testSecurityPropertyTruststore() { + GrouperConfig.retrieveConfig().propertiesOverrideMap().put("grouper.osgi.plugin.test.location", FrameworkStarterTests.class.getResource("/plugins/grouper-test-plugin.jar").toString()); + GrouperConfig.retrieveConfig().propertiesOverrideMap().put("grouper.osgi.truststore.signing.certificate", """ + -----BEGIN CERTIFICATE----- + MIIDczCCAlugAwIBAgIILehkAIY5xvQwDQYJKoZIhvcNAQELBQAwaDEQMA4GA1UE + BhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQ + MA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEMMAoGA1UEAwwDSmoh + MB4XDTIzMTAxMjAwMjE1OFoXDTMzMTAxMTAwMjE1OFowaDEQMA4GA1UEBhMHVW5r + bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE + ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEMMAoGA1UEAwwDSmohMIIBIjAN + BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Y8HkztTbh1gXW+lIB175CYiqR5U + qPVaCe9efvLd89zzyKpO1wxdp7lbJwwHfepuFhsGPMl38aD6YXeQDRKj3m7u2m94 + I1PAiAYslRSN/3oWyv7RQwP3gn4XAegpR4kBCNJoXLTPK80oyYA62okVNtCH/dFq + Z3k3A1O+OoeqcDBq5KzuPatViFAjEyyLCcV1g0mLQlCfzrWFw5OVp2U6q2D0qLKF + 5i2bFkIlwxcWY0tfvv0S8Lp7+JpPN/etXGByKigM4Cu9CnfLBUs0v9h4wH5wv/Zj + /XGfViD+pemIgkR82BqPM31TeWU0ONOYapS0LS4RpZBOcLtXcwgBMIch2QIDAQAB + oyEwHzAdBgNVHQ4EFgQU5cbJ7MBtBDej6XYd3iukheyNO5kwDQYJKoZIhvcNAQEL + BQADggEBAMjtgjBespxleA5Po49A3kng/A1n24m0SuGDVRmtWm5OGi34e+A64GIo + kgiux+r0HOSDHUaIfao1L84FIxIBGGSHW/hcZ8RJr1qSYvW7KnD9PfgAcghtokKx + 38fx3Q3RUDB6Gcz2vigTkJ7OzR1wkyzVvfnLEOL3kA1P49Eb/wyMKEmmLDQyij6n + JG+T6+M4+QKiA8W9yIIAAjJFoNL0LBczsdVn6o34buwqLPhjhVoGazDbax5cXxkJ + 8TRdktqQNbBqDk+6mOto8MCJUtg7d+Run+HvtgjNsdYLI6J+6K9+51STLtXST90V + voa3jXptx9Tl/BFoJK2sJWdoIwG3zug= + -----END CERTIFICATE-----"""); + + FrameworkStarter.getInstance().start(); + } + + @Test + public void testSecurityUntrustedSigner() { + GrouperConfig.retrieveConfig().propertiesOverrideMap().put(FrameworkStarter.GROUPER_OSGI_FRAMEWORK_TRUST_REPOSITORIES, FrameworkStarterTests.class.getResource("/plugins/test-empty.jks").getPath()); + GrouperConfig.retrieveConfig().propertiesOverrideMap().put("grouper.osgi.plugin.test.location", FrameworkStarterTests.class.getResource("/plugins/grouper-test-plugin.jar").toString()); + + Exception e = assertThrows(Exception.class, () -> FrameworkStarter.getInstance().start()); + assert(e.getCause().getCause() instanceof IOException); + } + + @Test + public void testSecurityCorruptJar() { + GrouperConfig.retrieveConfig().propertiesOverrideMap().put(FrameworkStarter.GROUPER_OSGI_FRAMEWORK_TRUST_REPOSITORIES, FrameworkStarterTests.class.getResource("/plugins/test.jks").getPath()); + GrouperConfig.retrieveConfig().propertiesOverrideMap().put("grouper.osgi.plugin.test.location", FrameworkStarterTests.class.getResource("/plugins/grouper-test-plugin-corrupt.jar").toString()); + + Exception e = assertThrows(GrouperPluginException.class, () -> FrameworkStarter.getInstance().start()); + assertTrue(e.getCause().getCause() instanceof IOException); + } +} diff --git a/grouper/src/test/plugins/grouper-test-plugin-corrupt.jar b/grouper/src/test/plugins/grouper-test-plugin-corrupt.jar new file mode 100644 index 000000000000..8d465efd7f9a Binary files /dev/null and b/grouper/src/test/plugins/grouper-test-plugin-corrupt.jar differ diff --git a/grouper/src/test/plugins/grouper-test-plugin.jar b/grouper/src/test/plugins/grouper-test-plugin.jar new file mode 100644 index 000000000000..70a5a9c4ea5b Binary files /dev/null and b/grouper/src/test/plugins/grouper-test-plugin.jar differ diff --git a/grouper/src/test/plugins/test-empty.jks b/grouper/src/test/plugins/test-empty.jks new file mode 100644 index 000000000000..7a094b403274 Binary files /dev/null and b/grouper/src/test/plugins/test-empty.jks differ diff --git a/grouper/src/test/plugins/test.jks b/grouper/src/test/plugins/test.jks new file mode 100644 index 000000000000..d6cc98835617 Binary files /dev/null and b/grouper/src/test/plugins/test.jks differ