This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 4_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/4_1_X by this push:
     new d81abf5b01 Improving Groovy handling (#1368)
d81abf5b01 is described below

commit d81abf5b01fe6b8943cdd913ccf657b34be82840
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                                            | 12 +--
 7 files changed, 116 insertions(+), 59 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 ba5098ce00..8d1295779e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2209,19 +2209,19 @@ under the License.
             <link>https://www.slf4j.org/api/</link>
             <link>https://connid.tirasa.net/apidocs/1.6/</link>
             <link>https://cxf.apache.org/javadoc/latest/</link>
-            
<link>https://javadoc.io/static/com.fasterxml.jackson.core/jackson-core/2.19.4/</link>
-            
<link>https://javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.19.4/</link>
-            
<link>https://javadoc.io/static/com.fasterxml.jackson.core/jackson-annotations/2.19.4/</link>
-            
<link>https://javadoc.io/static/com.fasterxml.jackson.dataformat/jackson-dataformat-csv/2.19.4/</link>
+            
<link>https://javadoc.io/static/com.fasterxml.jackson.core/jackson-core/2.21.2/</link>
+            
<link>https://javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.21.2/</link>
+            
<link>https://javadoc.io/static/com.fasterxml.jackson.core/jackson-annotations/2.21.2/</link>
+            
<link>https://javadoc.io/static/com.fasterxml.jackson.dataformat/jackson-dataformat-csv/2.21.2/</link>
             <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.spring.io/spring-boot/3.5/api/java/</link>
             
<link>https://docs.spring.io/spring-framework/docs/6.2.x/javadoc-api/</link>
             
<link>https://docs.spring.io/spring-security/site/docs/6.5.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://docs.swagger.io/swagger-core/v2.2.49/apidocs/</link>
           </links>
           <additionalOptions>
             <additionalOption>--legal-notices</additionalOption>

Reply via email to