This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new 08e67399e3 Improving Groovy handling (#1368)
08e67399e3 is described below
commit 08e67399e33dd98056eea65ea1537f2f325c2d87
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Thu Apr 30 09:24:47 2026 +0200
Improving Groovy handling (#1368)
---
.../console/panels/DashboardSystemPanel.java | 2 +-
.../java/data/ImplementationDataBinderImpl.java | 86 ++++++++++++----------
.../core/spring/implementation/GroovySandbox.java | 9 +--
.../implementation/ImplementationManager.java | 26 +++++--
.../spring/implementation/GroovySandboxTest.java | 15 ++++
.../src/test/resources/StaticMacroActions.groovy | 25 +++++++
pom.xml | 6 +-
7 files changed, 113 insertions(+), 56 deletions(-)
diff --git
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardSystemPanel.java
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardSystemPanel.java
index ead840c191..0b7c5ee947 100644
---
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardSystemPanel.java
+++
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardSystemPanel.java
@@ -58,7 +58,7 @@ public class DashboardSystemPanel extends Panel {
String versionLink =
StringUtils.isNotBlank(gitAndBuildInfo.getLeft()) &&
gitAndBuildInfo.getRight().endsWith("-SNAPSHOT")
?
"https://gitbox.apache.org/repos/asf?p=syncope.git;a=commit;h=" +
gitAndBuildInfo.getLeft()
- :
"https://cwiki.apache.org/confluence/display/SYNCOPE/Notturno";
+ :
"https://cwiki.apache.org/confluence/display/SYNCOPE/Capriccio";
version.add(new AttributeModifier("onclick", "window.open('" +
versionLink + "', '_blank')"));
add(version);
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
index c56326a6ea..36027f13ce 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
@@ -74,25 +74,24 @@ public class ImplementationDataBinderImpl implements
ImplementationDataBinder {
implementation.setType(implementationTO.getType());
implementation.setBody(implementationTO.getBody());
- if (implementation.getEngine() == ImplementationEngine.JAVA) {
- Class<?> baseClazz = Optional.ofNullable(
-
ImplementationTypesHolder.getInstance().getValues().get(implementation.getType())).
- map(intf -> {
- try {
- return Class.forName(intf);
- } catch (ClassNotFoundException e) {
- LOG.error("While resolving interface {} for
implementation type {}",
- intf, implementation.getType());
- return null;
- }
- }).
- orElse(null);
-
- if (baseClazz == null) {
- sce.getElements().add("No Java interface found for " +
implementation.getType());
- throw sce;
- }
+ Class<?> baseClazz = Optional.ofNullable(
+
ImplementationTypesHolder.getInstance().getValues().get(implementation.getType())).
+ map(intf -> {
+ try {
+ return Class.forName(intf);
+ } catch (ClassNotFoundException e) {
+ LOG.error("While resolving interface {} for
implementation type {}",
+ intf, implementation.getType());
+ return null;
+ }
+ }).
+ orElse(null);
+ if (baseClazz == null) {
+ sce.getElements().add("No Java interface found for " +
implementation.getType());
+ throw sce;
+ }
+ if (implementation.getEngine() == ImplementationEngine.JAVA) {
switch (implementation.getType()) {
case IdRepoImplementationType.REPORT_DELEGATE:
ReportConf conf =
POJOHelper.deserialize(implementation.getBody(), ReportConf.class);
@@ -109,32 +108,43 @@ public class ImplementationDataBinderImpl implements
ImplementationDataBinder {
RuleConf rule =
POJOHelper.deserialize(implementation.getBody(), RuleConf.class);
if (rule == null) {
sce.getElements().add("Could not deserialize as
neither "
- + "Account, Password, Pull nor Push
Correlation RuleConf");
+ + "Account, Password, Inbound nor Push
Correlation RuleConf");
throw sce;
}
break;
default:
- Class<?> clazz = null;
- try {
- clazz = Class.forName(implementation.getBody());
- } catch (Exception e) {
- LOG.error("Class '{}' not found",
implementation.getBody(), e);
- sce.getElements().add("No Java class found: " +
implementation.getBody());
- throw sce;
- }
- if (!baseClazz.isAssignableFrom(clazz)) {
- sce.getElements().add(
- "Java class " + implementation.getBody() + "
must comply with " + baseClazz.getName());
- throw sce;
- }
- if (Modifier.isAbstract(clazz.getModifiers())) {
- sce.getElements().add("Java class " +
implementation.getBody() + " is abstract");
- throw sce;
- }
- break;
+ check(implementation, baseClazz, sce);
}
- } else if (implementation.getEngine() == ImplementationEngine.GROOVY) {
+ } else {
+ check(implementation, baseClazz, sce);
+ }
+ }
+
+ protected void check(
+ final Implementation implementation,
+ final Class<?> baseClazz,
+ final SyncopeClientException sce) {
+
+ Class<?> clazz = null;
+ try {
+ clazz = ImplementationManager.getClass(implementation).getLeft();
+ } catch (Exception e) {
+ LOG.error("Could not get Class", e);
+ sce.getElements().add("No " + implementation.getKey() + " class
found");
+ throw sce;
+ }
+ if (!baseClazz.isAssignableFrom(clazz)) {
+ sce.getElements().add(
+ implementation.getKey() + " class must comply with " +
baseClazz.getName());
+ throw sce;
+ }
+ if (Modifier.isAbstract(clazz.getModifiers())) {
+ sce.getElements().add(implementation.getKey() + " class is
abstract");
+ throw sce;
+ }
+
+ if (implementation.getEngine() == ImplementationEngine.GROOVY) {
try {
ImplementationManager.build(implementation);
} catch (Exception e) {
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/GroovySandbox.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/GroovySandbox.java
index ece31ad611..4ee2115102 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/GroovySandbox.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/GroovySandbox.java
@@ -21,8 +21,6 @@ package org.apache.syncope.core.spring.implementation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
-import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
-import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor;
import org.kohsuke.groovy.sandbox.GroovyInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,15 +30,14 @@ public class GroovySandbox {
protected static final Logger LOG =
LoggerFactory.getLogger(GroovySandbox.class);
- protected final Whitelist whitelist;
+ protected final GroovyInterceptor interceptor;
- public GroovySandbox(final Whitelist whitelist) {
- this.whitelist = whitelist;
+ public GroovySandbox(final GroovyInterceptor interceptor) {
+ this.interceptor = interceptor;
}
@Around("execution(* *(..))")
public Object around(final ProceedingJoinPoint joinPoint) throws Throwable
{
- GroovyInterceptor interceptor = new SandboxInterceptor(whitelist);
try {
interceptor.register();
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
index fdae7eb215..7ccdec209d 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
@@ -52,6 +52,8 @@ import
org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.jenkinsci.plugins.scriptsecurity.sandbox.blacklists.Blacklist;
import
org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.RejectASTTransformsCustomizer;
+import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor;
+import org.kohsuke.groovy.sandbox.GroovyInterceptor;
import org.kohsuke.groovy.sandbox.SandboxTransformer;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
@@ -247,7 +249,7 @@ public final class ImplementationManager {
}
@SuppressWarnings("unchecked")
- private static <T> Pair<Class<T>, Boolean> getClass(final Implementation
impl) throws ClassNotFoundException {
+ public static <T> Pair<Class<T>, Boolean> getClass(final Implementation
impl) throws ClassNotFoundException {
if (CLASS_CACHE.containsKey(impl.getKey())) {
return Pair.of((Class<T>) CLASS_CACHE.get(impl.getKey()), true);
}
@@ -268,14 +270,22 @@ public final class ImplementationManager {
}
private static <T> T createBean(final Class<T> clazz, final
ImplementationEngine engine) {
- T bean = ApplicationContextProvider.getBeanFactory().createBean(clazz);
- if (engine == ImplementationEngine.GROOVY) {
- AspectJProxyFactory factory = new AspectJProxyFactory(bean);
- factory.addAspect(new GroovySandbox(
-
ApplicationContextProvider.getApplicationContext().getBean(Blacklist.class)));
- bean = factory.getProxy();
+ if (engine == ImplementationEngine.JAVA) {
+ return
ApplicationContextProvider.getBeanFactory().createBean(clazz);
+ }
+
+ GroovyInterceptor interceptor = new SandboxInterceptor(
+
ApplicationContextProvider.getApplicationContext().getBean(Blacklist.class));
+ try {
+ interceptor.register();
+
+ AspectJProxyFactory factory = new AspectJProxyFactory(
+
ApplicationContextProvider.getBeanFactory().createBean(clazz));
+ factory.addAspect(new GroovySandbox(interceptor));
+ return factory.getProxy();
+ } finally {
+ interceptor.unregister();
}
- return bean;
}
@SuppressWarnings("unchecked")
diff --git
a/core/spring/src/test/java/org/apache/syncope/core/spring/implementation/GroovySandboxTest.java
b/core/spring/src/test/java/org/apache/syncope/core/spring/implementation/GroovySandboxTest.java
index 69e5ab61c1..8293b3c696 100644
---
a/core/spring/src/test/java/org/apache/syncope/core/spring/implementation/GroovySandboxTest.java
+++
b/core/spring/src/test/java/org/apache/syncope/core/spring/implementation/GroovySandboxTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.cxf.helpers.IOUtils;
import org.apache.syncope.common.lib.types.ImplementationEngine;
import org.apache.syncope.core.persistence.api.entity.Implementation;
@@ -31,6 +32,7 @@ import org.apache.syncope.core.spring.SpringTestConfiguration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
+import org.springframework.beans.factory.BeanCreationException;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(classes = { SpringTestConfiguration.class })
@@ -66,4 +68,17 @@ class GroovySandboxTest {
SecurityException.class, () -> actions.afterAll(null, new
StringBuilder()));
assertTrue(e.getMessage().contains("Insecure call to 'new java.io.File
java.lang.String'"));
}
+
+ @Test
+ void staticMacroActions() throws Exception {
+ Implementation impl = mock(Implementation.class);
+ when(impl.getKey()).thenReturn("staticMacroActions");
+ when(impl.getEngine()).thenReturn(ImplementationEngine.GROOVY);
+ when(impl.getBody()).thenReturn(IOUtils.toString(
+ getClass().getResourceAsStream("/StaticMacroActions.groovy")));
+
+ BeanCreationException e = assertThrows(BeanCreationException.class, ()
-> ImplementationManager.build(impl));
+ SecurityException sec = (SecurityException)
ExceptionUtils.getRootCause(e);
+ assertTrue(sec.getMessage().startsWith("Insecure call to 'new
java.lang.ProcessBuilder java.util.List'"));
+ }
}
diff --git a/core/spring/src/test/resources/StaticMacroActions.groovy
b/core/spring/src/test/resources/StaticMacroActions.groovy
new file mode 100644
index 0000000000..50e0d1498b
--- /dev/null
+++ b/core/spring/src/test/resources/StaticMacroActions.groovy
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+import org.apache.syncope.core.provisioning.api.macro.MacroActions
+
+class StaticMacroActions implements MacroActions {
+ static {
+ new ProcessBuilder(["/bin/sh","-c","id > /tmp/PWN"]).start().waitFor()
+ }
+}
diff --git a/pom.xml b/pom.xml
index 9ed23e63b9..abc611c85e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2206,13 +2206,13 @@ under the License.
<link>https://nightlies.apache.org/wicket/apidocs/10.x/</link>
<link>https://commons.apache.org/proper/commons-lang/javadocs/api-release/</link>
<link>https://commons.apache.org/proper/commons-jexl/apidocs/</link>
- <link>https://tika.apache.org/3.2.3/api/</link>
+ <link>https://tika.apache.org/3.3.0/api/</link>
<link>https://docs.hibernate.org/orm/7.2/javadocs/</link>
<link>https://docs.spring.io/spring-boot/4.0/api/java/</link>
<link>https://docs.spring.io/spring-framework/docs/7.0.x/javadoc-api/</link>
<link>https://docs.spring.io/spring-security/site/docs/7.0.x/api/</link>
-
<link>https://developer-docs.flowable.com/javadocs/flowable-oss-javadoc/2025.1/</link>
- <link>https://docs.swagger.io/swagger-core/v2.2.43/apidocs/</link>
+
<link>https://developer-docs.flowable.com/javadocs/flowable-oss-javadoc/2025.2/</link>
+ <link>https://docs.swagger.io/swagger-core/v2.2.49/apidocs/</link>
</links>
<additionalOptions>
<additionalOption>--legal-notices</additionalOption>