CVE-2024-31997: Insufficient Information (NVD-CWE-noinfo)
value: XWiki Platform is a generic wiki platform. Prior to versions 4.10.19, 15.5.4, and 15.10-rc-1, parameters of UI extensions are always interpreted as Velocity code and executed with programming rights. Any user with edit right on any document like the user's own profile can create UI extensions. This allows remote code execution and thereby impacts the confidentiality, integrity and availability of the whole XWiki installation. This vulnerability has been patched in XWiki 14.10.19, 15.5.4 and 15.9-RC1. No known workarounds are available.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-10T22:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Java
Files changed: xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionComponentBuilder.java, xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionParameters.java, xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionComponentBuilderTest.java, xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionParametersTest.java
Commit date: 2023-10-17 15:21:41 +0200
Commit message: XWIKI-21335: Execute UI Extension parameters with the rights of their author

* Check script right for the UI extension's author
* Use AuthorExecutor to set the context author

Diff: diff --git a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionComponentBuilder.java b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionComponentBuilder.java
index 8676fb8a4a9a7..9cc54c9f3ec39 100644
--- a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionComponentBuilder.java
+++ b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionComponentBuilder.java
@@ -134,16 +134,13 @@ public List<WikiComponent> buildComponents(BaseObject baseObject) throws WikiCom
         } catch (Exception e) {
             throw new WikiComponentException(
                 String.format("Failed to initialize Panel UI extension [%s]", baseObject.getReference()), e);
         }
 
-        String rawParameters = baseObject.getStringValue(PARAMETERS_PROPERTY);
-
         // It would be nice to have PER_LOOKUP components for UIX parameters but without constructor injection it's
         // safer to use a POJO and pass the Component Manager to it.
-        WikiUIExtensionParameters parameters =
-            new WikiUIExtensionParameters(id, rawParameters, this.wikiComponentManager);
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(baseObject, this.wikiComponentManager);
         extension.setParameters(parameters);
         extension.setScope(scope);
 
         return Collections.singletonList(extension);
     }
diff --git a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionParameters.java b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionParameters.java
index 4cd93a6e1d19e..e19e1da2cea14 100644
--- a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionParameters.java
+++ b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/main/java/org/xwiki/uiextension/internal/WikiUIExtensionParameters.java
@@ -20,13 +20,14 @@
 package org.xwiki.uiextension.internal;
 
 import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.velocity.VelocityContext;
 import org.slf4j.Logger;
@@ -36,15 +37,24 @@
 import org.xwiki.component.wiki.WikiComponentException;
 import org.xwiki.context.Execution;
 import org.xwiki.logging.LoggerConfiguration;
 import org.xwiki.model.EntityType;
 import org.xwiki.model.ModelContext;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.security.authorization.AuthorExecutor;
+import org.xwiki.security.authorization.AuthorizationManager;
+import org.xwiki.security.authorization.Right;
 import org.xwiki.velocity.VelocityEngine;
 import org.xwiki.velocity.VelocityManager;
 import org.xwiki.velocity.XWikiVelocityContext;
 import org.xwiki.velocity.XWikiVelocityException;
 
+import com.xpn.xwiki.objects.BaseObject;
+
+import static org.xwiki.uiextension.internal.WikiUIExtensionConstants.ID_PROPERTY;
+import static org.xwiki.uiextension.internal.WikiUIExtensionConstants.PARAMETERS_PROPERTY;
+
 /**
  * Wiki UI Extension parameter manager.
  *
  * @version $Id$
  * @since 5.0M1
@@ -95,29 +105,41 @@
     /**
      * The execution context.
      */
     private Execution execution;
 
+    private AuthorExecutor authorExecutor;
+
+    private final DocumentReference documentReference;
+
+    private final DocumentReference authorReference;
+
+    private final AuthorizationManager authorizationManager;
+
     /**
      * Default constructor.
      *
-     * @param id the unique identifier of this set of parameters, mostly used to isolate parameters value execution
-     * @param rawParameters raw parameters, their values can contain velocity directives
+     * @param baseObject the object from which the parameters shall be loaded
      * @param cm the XWiki component manager
      * @throws WikiComponentException if some required components can't be found in the Component Manager
      */
-    public WikiUIExtensionParameters(String id, String rawParameters, ComponentManager cm)
+    public WikiUIExtensionParameters(BaseObject baseObject, ComponentManager cm)
         throws WikiComponentException
     {
-        this.id = id;
-        this.parameters = parseParameters(rawParameters);
+        this.id = baseObject.getStringValue(ID_PROPERTY);
+        this.parameters = parseParameters(baseObject.getStringValue(PARAMETERS_PROPERTY));
+
+        this.documentReference = baseObject.getDocumentReference();
+        this.authorReference = baseObject.getOwnerDocument().getAuthorReference();
 
         try {
             this.execution = cm.getInstance(Execution.class);
             this.velocityManager = cm.getInstance(VelocityManager.class);
             this.modelContext = cm.getInstance(ModelContext.class);
             this.loggerConfiguration = cm.getInstance(LoggerConfiguration.class);
+            this.authorExecutor = cm.getInstance(AuthorExecutor.class);
+            this.authorizationManager = cm.getInstance(AuthorizationManager.class);
         } catch (ComponentLookupException e) {
             throw new WikiComponentException(
                 "Failed to get an instance for a component role required by Wiki Components.", e);
         }
     }
@@ -146,55 +168,62 @@ private Properties parseParameters(String rawParameters)
     /**
      * @return the parameters, after their values have been evaluated by the XWiki Velocity Engine.
      */
     public Map<String, String> get()
     {
-        boolean isCacheValid = false;
+        Map<String, String> result;
 
         // Even though the parameters are dynamic, we cache a rendered version of them in order to improve performance.
         // This cache has a short lifespan, it gets discarded for each new request, or if the database has been switched
         // during a request.
         int currentContextId = this.execution.getContext().hashCode();
         String currentWiki = modelContext.getCurrentEntityReference().extractReference(EntityType.WIKI).getName();
         if (currentContextId == this.previousContextId
                 && currentWiki.equals(previousWiki) && this.evaluatedParameters != null)
         {
-            isCacheValid = true;
-        }
-
-        if (!isCacheValid) {
-            this.evaluatedParameters = new HashMap<>();
-
-            if (this.parameters.size() > 0) {
+            result = this.evaluatedParameters;
+        } else {
+            result = this.parameters.stringPropertyNames().stream()
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toMap(Function.identity(), this.parameters::getProperty));
+
+            if (!this.parameters.isEmpty()
+                && this.authorizationManager.hasAccess(Right.SCRIPT, this.authorReference, this.documentReference))
+            {
                 try {
-                    VelocityEngine velocityEngine = this.velocityManager.getVelocityEngine();
-                    VelocityContext velocityContext = this.velocityManager.getVelocityContext();
+                    this.authorExecutor.call(() -> {
+                        VelocityEngine velocityEngine = this.velocityManager.getVelocityEngine();
+                        VelocityContext velocityContext = this.velocityManager.getVelocityContext();
 
-                    for (String propertyKey : this.parameters.stringPropertyNames()) {
-                        if (!StringUtils.isBlank(propertyKey)) {
-                            String propertyValue = this.parameters.getProperty(propertyKey);
+                        result.replaceAll((propertyKey, propertyValue) -> {
                             StringWriter writer = new StringWriter();
                             try {
                                 String namespace = this.id + ':' + propertyKey;
                                 velocityEngine.evaluate(
                                     new XWikiVelocityContext(velocityContext,
                                         this.loggerConfiguration.isDeprecatedLogEnabled()),
                                     writer, namespace, propertyValue);
-                                this.evaluatedParameters.put(propertyKey, writer.toString());
+                                return writer.toString();
                             } catch (XWikiVelocityException e) {
                                 LOGGER.warn(String.format(
                                     "Failed to evaluate UI extension data value, key [%s], value [%s]. Reason: [%s]",
                                     propertyKey, propertyValue, e.getMessage()));
                             }
-                        }
-                    }
-                } catch (XWikiVelocityException ex) {
+
+                            return propertyValue;
+                        });
+
+                        return null;
+                    }, this.authorReference, this.documentReference);
+                } catch (Exception ex) {
                     LOGGER.warn(String.format("Failed to get velocity engine. Reason: [%s]", ex.getMessage()));
                 }
-                this.previousContextId = currentContextId;
-                this.previousWiki = currentWiki;
             }
+
+            this.evaluatedParameters = result;
+            this.previousContextId = currentContextId;
+            this.previousWiki = currentWiki;
         }
 
-        return this.evaluatedParameters;
+        return result;
     }
 }
diff --git a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionComponentBuilderTest.java b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionComponentBuilderTest.java
index 74ff85e118fe5..704f1219b9d62 100644
--- a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionComponentBuilderTest.java
+++ b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionComponentBuilderTest.java
@@ -21,10 +21,11 @@
 
 import java.io.StringWriter;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 import org.apache.velocity.VelocityContext;
 import org.junit.jupiter.api.Test;
 import org.xwiki.component.internal.multi.DelegateComponentManager;
 import org.xwiki.component.manager.ComponentManager;
@@ -41,10 +42,13 @@
 import org.xwiki.rendering.async.internal.block.BlockAsyncRendererExecutor;
 import org.xwiki.rendering.syntax.Syntax;
 import org.xwiki.rendering.transformation.RenderingContext;
 import org.xwiki.rendering.transformation.Transformation;
 import org.xwiki.rendering.util.ErrorBlockGenerator;
+import org.xwiki.security.authorization.AuthorExecutor;
+import org.xwiki.security.authorization.AuthorizationManager;
+import org.xwiki.security.authorization.Right;
 import org.xwiki.test.annotation.BeforeComponent;
 import org.xwiki.test.annotation.ComponentList;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
 import org.xwiki.test.junit5.mockito.MockComponent;
 import org.xwiki.test.mockito.MockitoComponentManager;
@@ -91,10 +95,16 @@ class WikiUIExtensionComponentBuilderTest implements WikiUIExtensionConstants
     private ContentParser contentParser;
 
     @MockComponent
     private LoggerConfiguration loggerConfiguration;
 
+    @MockComponent
+    private AuthorExecutor authorExecutor;
+
+    @MockComponent
+    private AuthorizationManager authorizationManager;
+
     @InjectMockComponents
     private WikiUIExtensionComponentBuilder builder;
 
     private static final DocumentReference DOC_REF =
         new DocumentReference("xwiki", "XWiki", "MyUIExtension", Locale.ROOT);
@@ -126,10 +136,17 @@ public void configure(MockitoComponentManager componentManager, MockitoOldcore o
 
         componentManager.registerMockComponent(RenderingContext.class);
         componentManager.registerMockComponent(Transformation.class, "macro");
         componentManager.registerMockComponent(ContentParser.class);
 
+        when(this.authorExecutor.call(any(), any(), any())).thenAnswer(invocation -> {
+            Callable<?> callable = invocation.getArgument(0);
+            return callable.call();
+        });
+
+        when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, DOC_REF)).thenReturn(true);
+
         // The document holding the UI extension object.
         this.componentDoc = mock(XWikiDocument.class, "xwiki:XWiki.MyUIExtension");
         when(this.componentDoc.getDocumentReference()).thenReturn(DOC_REF);
         when(this.componentDoc.getAuthorReference()).thenReturn(AUTHOR_REFERENCE);
         when(this.componentDoc.getContentAuthorReference()).thenReturn(AUTHOR_REFERENCE);
@@ -195,10 +212,11 @@ private BaseObject createExtensionObject(String id, String extensionPointId, Str
         when(extensionObject.getStringValue(PARAMETERS_PROPERTY)).thenReturn(parameters);
         when(extensionObject.getStringValue(SCOPE_PROPERTY)).thenReturn(scope);
         BaseObjectReference objectReference =
             new BaseObjectReference(new ObjectReference("XWiki.UIExtensionClass[0]", DOC_REF));
         when(extensionObject.getReference()).thenReturn(objectReference);
+        when(extensionObject.getDocumentReference()).thenReturn(DOC_REF);
 
         when(extensionObject.getOwnerDocument()).thenReturn(this.componentDoc);
 
         return extensionObject;
     }
diff --git a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionParametersTest.java b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionParametersTest.java
index 8b88dde59e236..51fba9f876d52 100644
--- a/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionParametersTest.java
+++ b/xwiki-platform-core/xwiki-platform-uiextension/xwiki-platform-uiextension-api/src/test/java/org/xwiki/uiextension/WikiUIExtensionParametersTest.java
@@ -18,10 +18,11 @@
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
 package org.xwiki.uiextension;
 
 import java.io.StringWriter;
+import java.util.concurrent.Callable;
 
 import org.apache.commons.collections.MapUtils;
 import org.apache.velocity.VelocityContext;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -29,29 +30,39 @@
 import org.mockito.Mock;
 import org.xwiki.context.Execution;
 import org.xwiki.context.ExecutionContext;
 import org.xwiki.logging.LoggerConfiguration;
 import org.xwiki.model.ModelContext;
+import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.model.reference.WikiReference;
+import org.xwiki.security.authorization.AuthorExecutor;
+import org.xwiki.security.authorization.AuthorizationManager;
+import org.xwiki.security.authorization.Right;
 import org.xwiki.test.LogLevel;
 import org.xwiki.test.junit5.LogCaptureExtension;
 import org.xwiki.test.junit5.mockito.ComponentTest;
 import org.xwiki.test.junit5.mockito.InjectComponentManager;
 import org.xwiki.test.junit5.mockito.MockComponent;
 import org.xwiki.test.mockito.MockitoComponentManager;
+import org.xwiki.uiextension.internal.WikiUIExtensionConstants;
 import org.xwiki.uiextension.internal.WikiUIExtensionParameters;
 import org.xwiki.velocity.VelocityEngine;
 import org.xwiki.velocity.VelocityManager;
 import org.xwiki.velocity.XWikiVelocityException;
 
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 /**
  * Unit tests for {@link WikiUIExtensionParametersTest}.
  *
@@ -59,10 +70,14 @@
  * @since 5.0M1
  */
 @ComponentTest
 class WikiUIExtensionParametersTest
 {
+    private static final DocumentReference AUTHOR_REFERENCE = new DocumentReference("xwiki", "XWiki", "XWikiAdmin");
+
+    private static final DocumentReference DOCUMENT_REFERENCE = new DocumentReference("xwiki", "Space", "UIX");
+
     @Mock
     private VelocityEngine velocityEngine;
 
     @MockComponent
     private ModelContext modelContext;
@@ -74,10 +89,16 @@
     private VelocityManager velocityManager;
 
     @MockComponent
     private LoggerConfiguration loggerConfiguration;
 
+    @MockComponent
+    private AuthorExecutor authorExecutor;
+
+    @MockComponent
+    private AuthorizationManager authorizationManager;
+
     @RegisterExtension
     private final LogCaptureExtension logCapture = new LogCaptureExtension(LogLevel.WARN);
 
     @InjectComponentManager
     private MockitoComponentManager componentManager;
@@ -89,92 +110,97 @@ public void setUp() throws Exception
         ExecutionContext executionContext = mock(ExecutionContext.class);
 
         when(this.execution.getContext()).thenReturn(executionContext);
         when(this.velocityManager.getVelocityContext()).thenReturn(velocityContext);
         when(this.velocityManager.getVelocityEngine()).thenReturn(this.velocityEngine);
+
+        when(this.authorExecutor.call(any(), any(), any())).thenAnswer(invocation -> {
+            Callable<?> callable = invocation.getArgument(0);
+            return callable.call();
+        });
+
+        when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, DOCUMENT_REFERENCE)).thenReturn(true);
+
+        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
     }
 
     @Test
     void getParametersWithAnEmptyParametersProperty() throws Exception
     {
-        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
-        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "", this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject("");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
         assertEquals(MapUtils.EMPTY_MAP, parameters.get());
     }
 
     @Test
     void getParametersWithAnEqualSignInAValue() throws Exception
     {
-        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
         when(this.velocityEngine
             .evaluate(any(VelocityContext.class), any(StringWriter.class), eq("id:key"), eq("value")))
             .thenReturn(true);
-        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject("key=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
 
         // Since the StringWriter is created within the method, the value is "" and not "value".
         assertEquals("", parameters.get().get("key"));
     }
 
     @Test
     void getParametersWithCommentAloneOnLine() throws Exception
     {
-        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
-
         String paramsStr = "# a = 1\n"
             + "x=1\n"
             + "y=2\n"
             + "# ...\n"
             + "z=3";
-        WikiUIExtensionParameters parameters =
-            new WikiUIExtensionParameters("id", paramsStr, this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject(paramsStr);
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
         parameters.get();
 
         verify(this.velocityEngine).evaluate(any(), any(), eq("id:x"), eq("1"));
         verify(this.velocityEngine).evaluate(any(), any(), eq("id:y"), eq("2"));
         verify(this.velocityEngine).evaluate(any(), any(), eq("id:z"), eq("3"));
     }
 
     @Test
     void getParametersWithCommentEndOfLine() throws Exception
     {
-        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
-
         String paramsStr = "x=1##b\n"
             + "y=2####x\n"
             + "z=3 ## xyz\n";
-        WikiUIExtensionParameters parameters =
-            new WikiUIExtensionParameters("id", paramsStr, this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject(paramsStr);
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
         parameters.get();
 
         verify(this.velocityEngine).evaluate(any(), any(), eq("id:x"), eq("1##b"));
         verify(this.velocityEngine).evaluate(any(), any(), eq("id:y"), eq("2####x"));
         verify(this.velocityEngine).evaluate(any(), any(), eq("id:z"), eq("3 ## xyz"));
     }
 
     @Test
     void getParametersWhenVelocityFails() throws Exception
     {
-        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
         when(this.velocityEngine
             .evaluate(any(VelocityContext.class), any(StringWriter.class), eq("id:key"), eq("value")))
             .thenThrow(new XWikiVelocityException(""));
-        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject("key=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
 
-        // It should fail and put a warn in the logs
-        assertNull(parameters.get().get("key"));
+        // It put a warning in the logs and return the not-evaluated value.
+        assertEquals("value", parameters.get().get("key"));
         assertEquals("Failed to evaluate UI extension data value, key [key], value [value]. Reason: []",
             this.logCapture.getMessage(0));
     }
 
     @Test
     void getParametersFromTheSameRequestAndForTheSameWiki() throws Exception
     {
-        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
         when(this.velocityEngine
             .evaluate(any(VelocityContext.class), any(StringWriter.class), eq("id:key"), eq("value")))
             .thenReturn(true);
-        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject("key=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
 
         // It should fail silently
         assertEquals("", parameters.get().get("key"));
         assertEquals("", parameters.get().get("key"));
 
@@ -189,11 +215,12 @@ void getParametersFromTheSameRequestButForDifferentWikis() throws Exception
         when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("wiki1"))
             .thenReturn(new WikiReference("wiki2"));
         when(this.velocityEngine
             .evaluate(any(VelocityContext.class), any(StringWriter.class), eq("id:key"), eq("value")))
             .thenReturn(true);
-        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject("key=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
 
         // It should fail silently
         assertEquals("", parameters.get().get("key"));
         assertEquals("", parameters.get().get("key"));
 
@@ -203,15 +230,15 @@ void getParametersFromTheSameRequestButForDifferentWikis() throws Exception
     }
 
     @Test
     void getParametersFromDifferentRequests() throws Exception
     {
-        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("wiki1"));
         when(this.velocityEngine
             .evaluate(any(VelocityContext.class), any(StringWriter.class), eq("id:key"), eq("value")))
             .thenReturn(true);
-        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
+        BaseObject mockUIX = constructMockUIXObject("key=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
 
         ExecutionContext ec1 = mock(ExecutionContext.class, "ec1");
         ExecutionContext ec2 = mock(ExecutionContext.class, "ec2");
         when(this.execution.getContext()).thenReturn(ec1).thenReturn(ec2);
 
@@ -221,6 +248,53 @@ void getParametersFromDifferentRequests() throws Exception
 
         // Verify the velocity evaluation has been done for both wikis.
         verify(this.velocityEngine, times(2))
             .evaluate(any(VelocityContext.class), any(StringWriter.class), eq("id:key"), eq("value"));
     }
+
+    @Test
+    void getParametersWithoutScriptRight() throws Exception
+    {
+        when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, DOCUMENT_REFERENCE)).thenReturn(false);
+        BaseObject mockUIX = constructMockUIXObject("key=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
+
+        assertEquals("value", parameters.get().get("key"));
+        verifyNoInteractions(this.velocityEngine);
+    }
+
+    @Test
+    void getParametersWithAuthorExecutor() throws Exception
+    {
+        // Do not call the callable to check that the call to the Velocity engine is inside the author executor.
+        doReturn(null).when(this.authorExecutor).call(any(), any(), any());
+
+        BaseObject mockUIX = constructMockUIXObject("key=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
+
+        assertEquals("value", parameters.get().get("key"));
+        verify(this.authorExecutor).call(any(), eq(AUTHOR_REFERENCE), eq(DOCUMENT_REFERENCE));
+        verifyNoInteractions(this.velocityEngine);
+    }
+
+    @Test
+    void getParametersWithEmptyKey() throws Exception
+    {
+        BaseObject mockUIX = constructMockUIXObject("=value");
+        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
+
+        assertTrue(parameters.get().isEmpty());
+    }
+
+    private BaseObject constructMockUIXObject(String parameters)
+    {
+        BaseObject result = mock();
+
+        when(result.getStringValue(WikiUIExtensionConstants.ID_PROPERTY)).thenReturn("id");
+        when(result.getStringValue(WikiUIExtensionConstants.PARAMETERS_PROPERTY)).thenReturn(parameters);
+        when(result.getOwnerDocument()).thenReturn(mock(XWikiDocument.class));
+        when(result.getOwnerDocument().getAuthorReference()).thenReturn(WikiUIExtensionParametersTest.AUTHOR_REFERENCE);
+        when(result.getDocumentReference()).thenReturn(WikiUIExtensionParametersTest.DOCUMENT_REFERENCE);
+
+        return result;
+    }
 }

Vulnerable code:         String rawParameters = baseObject.getStringValue(PARAMETERS_PROPERTY);

        WikiUIExtensionParameters parameters =
            new WikiUIExtensionParameters(id, rawParameters, this.wikiComponentManager);
import java.util.HashMap;
     * @param id the unique identifier of this set of parameters, mostly used to isolate parameters value execution
     * @param rawParameters raw parameters, their values can contain velocity directives
    public WikiUIExtensionParameters(String id, String rawParameters, ComponentManager cm)
        this.id = id;
        this.parameters = parseParameters(rawParameters);
        boolean isCacheValid = false;
            isCacheValid = true;
        }

        if (!isCacheValid) {
            this.evaluatedParameters = new HashMap<>();

            if (this.parameters.size() > 0) {
                    VelocityEngine velocityEngine = this.velocityManager.getVelocityEngine();
                    VelocityContext velocityContext = this.velocityManager.getVelocityContext();
                    for (String propertyKey : this.parameters.stringPropertyNames()) {
                        if (!StringUtils.isBlank(propertyKey)) {
                            String propertyValue = this.parameters.getProperty(propertyKey);
                                this.evaluatedParameters.put(propertyKey, writer.toString());
                        }
                    }
                } catch (XWikiVelocityException ex) {
                this.previousContextId = currentContextId;
                this.previousWiki = currentWiki;
        return this.evaluatedParameters;
import static org.junit.jupiter.api.Assertions.assertNull;
        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "", this.componentManager);
        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));

        WikiUIExtensionParameters parameters =
            new WikiUIExtensionParameters("id", paramsStr, this.componentManager);
        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));

        WikiUIExtensionParameters parameters =
            new WikiUIExtensionParameters("id", paramsStr, this.componentManager);
        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
        // It should fail and put a warn in the logs
        assertNull(parameters.get().get("key"));
        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("wiki1"));
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters("id", "key=value", this.componentManager);
Fixed code:         WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(baseObject, this.wikiComponentManager);
import java.util.function.Function;
import java.util.stream.Collectors;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorExecutor;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import com.xpn.xwiki.objects.BaseObject;

import static org.xwiki.uiextension.internal.WikiUIExtensionConstants.ID_PROPERTY;
import static org.xwiki.uiextension.internal.WikiUIExtensionConstants.PARAMETERS_PROPERTY;

    private AuthorExecutor authorExecutor;

    private final DocumentReference documentReference;

    private final DocumentReference authorReference;

    private final AuthorizationManager authorizationManager;

     * @param baseObject the object from which the parameters shall be loaded
    public WikiUIExtensionParameters(BaseObject baseObject, ComponentManager cm)
        this.id = baseObject.getStringValue(ID_PROPERTY);
        this.parameters = parseParameters(baseObject.getStringValue(PARAMETERS_PROPERTY));

        this.documentReference = baseObject.getDocumentReference();
        this.authorReference = baseObject.getOwnerDocument().getAuthorReference();
            this.authorExecutor = cm.getInstance(AuthorExecutor.class);
            this.authorizationManager = cm.getInstance(AuthorizationManager.class);
        Map<String, String> result;
            result = this.evaluatedParameters;
        } else {
            result = this.parameters.stringPropertyNames().stream()
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toMap(Function.identity(), this.parameters::getProperty));

            if (!this.parameters.isEmpty()
                && this.authorizationManager.hasAccess(Right.SCRIPT, this.authorReference, this.documentReference))
            {
                    this.authorExecutor.call(() -> {
                        VelocityEngine velocityEngine = this.velocityManager.getVelocityEngine();
                        VelocityContext velocityContext = this.velocityManager.getVelocityContext();
                        result.replaceAll((propertyKey, propertyValue) -> {
                                return writer.toString();

                            return propertyValue;
                        });

                        return null;
                    }, this.authorReference, this.documentReference);
                } catch (Exception ex) {

            this.evaluatedParameters = result;
            this.previousContextId = currentContextId;
            this.previousWiki = currentWiki;
        return result;
import java.util.concurrent.Callable;
import org.xwiki.security.authorization.AuthorExecutor;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
    @MockComponent
    private AuthorExecutor authorExecutor;

    @MockComponent
    private AuthorizationManager authorizationManager;

        when(this.authorExecutor.call(any(), any(), any())).thenAnswer(invocation -> {
            Callable<?> callable = invocation.getArgument(0);
            return callable.call();
        });

        when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, DOC_REF)).thenReturn(true);

        when(extensionObject.getDocumentReference()).thenReturn(DOC_REF);
import java.util.concurrent.Callable;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorExecutor;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.uiextension.internal.WikiUIExtensionConstants;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verifyNoInteractions;
    private static final DocumentReference AUTHOR_REFERENCE = new DocumentReference("xwiki", "XWiki", "XWikiAdmin");

    private static final DocumentReference DOCUMENT_REFERENCE = new DocumentReference("xwiki", "Space", "UIX");

    @MockComponent
    private AuthorExecutor authorExecutor;

    @MockComponent
    private AuthorizationManager authorizationManager;


        when(this.authorExecutor.call(any(), any(), any())).thenAnswer(invocation -> {
            Callable<?> callable = invocation.getArgument(0);
            return callable.call();
        });

        when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, DOCUMENT_REFERENCE)).thenReturn(true);

        when(this.modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("xwiki"));
        BaseObject mockUIX = constructMockUIXObject("");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
        BaseObject mockUIX = constructMockUIXObject("key=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
        BaseObject mockUIX = constructMockUIXObject(paramsStr);
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
        BaseObject mockUIX = constructMockUIXObject(paramsStr);
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
        BaseObject mockUIX = constructMockUIXObject("key=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
        // It put a warning in the logs and return the not-evaluated value.
        assertEquals("value", parameters.get().get("key"));
        BaseObject mockUIX = constructMockUIXObject("key=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
        BaseObject mockUIX = constructMockUIXObject("key=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);
        BaseObject mockUIX = constructMockUIXObject("key=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);

    @Test
    void getParametersWithoutScriptRight() throws Exception
    {
        when(this.authorizationManager.hasAccess(Right.SCRIPT, AUTHOR_REFERENCE, DOCUMENT_REFERENCE)).thenReturn(false);
        BaseObject mockUIX = constructMockUIXObject("key=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);

        assertEquals("value", parameters.get().get("key"));
        verifyNoInteractions(this.velocityEngine);
    }

    @Test
    void getParametersWithAuthorExecutor() throws Exception
    {
        // Do not call the callable to check that the call to the Velocity engine is inside the author executor.
        doReturn(null).when(this.authorExecutor).call(any(), any(), any());

        BaseObject mockUIX = constructMockUIXObject("key=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);

        assertEquals("value", parameters.get().get("key"));
        verify(this.authorExecutor).call(any(), eq(AUTHOR_REFERENCE), eq(DOCUMENT_REFERENCE));
        verifyNoInteractions(this.velocityEngine);
    }

    @Test
    void getParametersWithEmptyKey() throws Exception
    {
        BaseObject mockUIX = constructMockUIXObject("=value");
        WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(mockUIX, this.componentManager);

        assertTrue(parameters.get().isEmpty());
    }

    private BaseObject constructMockUIXObject(String parameters)
    {
        BaseObject result = mock();

        when(result.getStringValue(WikiUIExtensionConstants.ID_PROPERTY)).thenReturn("id");
        when(result.getStringValue(WikiUIExtensionConstants.PARAMETERS_PROPERTY)).thenReturn(parameters);
        when(result.getOwnerDocument()).thenReturn(mock(XWikiDocument.class));
        when(result.getOwnerDocument().getAuthorReference()).thenReturn(WikiUIExtensionParametersTest.AUTHOR_REFERENCE);
        when(result.getDocumentReference()).thenReturn(WikiUIExtensionParametersTest.DOCUMENT_REFERENCE);

        return result;
    }