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

ashishvijaywargiya pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new fa45815a6d Fixed: CMS use-when expressions still templated into string 
literals (#1750)
fa45815a6d is described below

commit fa45815a6ddb9fb3f47420b3c95c552f0eb90e98
Author: Krishna Uprit <[email protected]>
AuthorDate: Thu Aug 27 17:05:33 2026 +0530

    Fixed: CMS use-when expressions still templated into string literals (#1750)
    
    Follow-up to
    
[d645c9096d](https://github.com/ashishvijaywargiya/ofbiz-framework/commit/d645c9096dad73e2876e41ee0368684f3c41cff2)
    (fix/cms-usewhen-groovy-eval-verify).
    
    - caContentAssocTypeId's use-when attributes in CMSForms.xml templated
    the value into a quoted string literal before evaluation, the same
    pattern already rewritten for caContentIdTo/caMapKey in the same form.
    
    - Rewrote caContentAssocTypeId to reference the context variable
    directly, matching the idiom used elsewhere.
    
    - Also converted caFromDate, caThruDate, contentId, and
    currentValue.dataResourceId in the same form to the same idiom for
    consistency.
    
    ---------
    
    Thank you Krishna Uprit for the contribution.
    
    Co-authored-by: Ashish Vijaywargiya <[email protected]>
    Co-authored-by: Krishnauprit18 <[email protected]>
---
 applications/content/widget/cms/CMSForms.xml       | 26 ++++----
 .../ofbiz/widget/model/ModelFormFieldTest.java     | 75 ++++++++++++++++++++++
 2 files changed, 88 insertions(+), 13 deletions(-)

diff --git a/applications/content/widget/cms/CMSForms.xml 
b/applications/content/widget/cms/CMSForms.xml
index 9fe93a72cc..1ca1d76f2d 100644
--- a/applications/content/widget/cms/CMSForms.xml
+++ b/applications/content/widget/cms/CMSForms.xml
@@ -377,27 +377,27 @@ under the License.
             <display description=""/>
         </field>
         <field name="caContentIdTo" position="1"
-            use-when="&quot;${caContentIdTo}&quot;.length()>0" >
+            use-when="caContentIdTo!=null&amp;&amp;caContentIdTo.length()>0" >
             <display />
         </field>
         <field name="caContentIdTo" position="1"
-            use-when="&quot;${caContentIdTo}&quot;.length()==0" >
+            use-when="caContentIdTo==null||caContentIdTo.length()==0" >
             <text />
         </field>
         <field name="caMapKey" position="1"
-            use-when="&quot;${caMapKey}&quot;.length()==0" >
+            use-when="caMapKey==null||caMapKey.length()==0" >
             <text />
         </field>
         <field name="caMapKey" position="1"
-            use-when="&quot;${caMapKey}&quot;.length()>0" >
+            use-when="caMapKey!=null&amp;&amp;caMapKey.length()>0" >
             <text />
         </field>
         <field name="caContentAssocTypeId" position="1"
-            use-when="&quot;${caContentAssocTypeId}&quot;.length()>0" >
+            
use-when="caContentAssocTypeId!=null&amp;&amp;caContentAssocTypeId.length()>0" >
             <display />
         </field>
         <field name="caContentAssocTypeId" position="1"
-            use-when="&quot;${caContentAssocTypeId}&quot;.length()==0" >
+            
use-when="caContentAssocTypeId==null||caContentAssocTypeId.length()==0" >
             <drop-down allow-empty="true">
                 <entity-options entity-name="ContentAssocType" 
key-field-name="contentAssocTypeId"/>
             </drop-down>
@@ -407,28 +407,28 @@ under the License.
                 <entity-options entity-name="MetaDataPredicate" 
key-field-name="metaDataPredicateId"/>
             </drop-down>
         </field>
-        <field name="caFromDate" title="${uiLabelMap.CommonFromDate}" 
widget-style="buttontext" position="1" 
use-when="&quot;${caFromDate}&quot;.length()>0">
+        <field name="caFromDate" title="${uiLabelMap.CommonFromDate}" 
widget-style="buttontext" position="1" use-when="caFromDate!=null">
             <display default-value="${nowTimestamp}"/>
         </field>
-        <field name="caFromDate" title="${uiLabelMap.CommonFromDate}" 
widget-style="buttontext" position="1" 
use-when="&quot;${caFromDate}&quot;.length()==0">
+        <field name="caFromDate" title="${uiLabelMap.CommonFromDate}" 
widget-style="buttontext" position="1" use-when="caFromDate==null">
             <date-time default-value="${nowTimestamp}"/>
         </field>
-        <field name="caThruDate" title="${uiLabelMap.CommonThruDate}" 
widget-style="buttontext" position="1" 
use-when="&quot;${caThruDate}&quot;.length()>0">
+        <field name="caThruDate" title="${uiLabelMap.CommonThruDate}" 
widget-style="buttontext" position="1" use-when="caThruDate!=null">
             <date-time/>
         </field>
-        <field name="caThruDate" title="${uiLabelMap.CommonThruDate}" 
widget-style="buttontext" position="1" 
use-when="&quot;${caThruDate}&quot;.length()==0">
+        <field name="caThruDate" title="${uiLabelMap.CommonThruDate}" 
widget-style="buttontext" position="1" use-when="caThruDate==null">
             <date-time/>
         </field>
         <field name="contentTitle" title="${uiLabelMap.ContentContent}" 
title-style="h1" map-name="dummy">
             <display description=""/>
         </field>
         <field name="contentId" position="1"
-            use-when="&quot;${currentValue.contentId}&quot;.length()>0" >
+            
use-when="currentValue.contentId!=null&amp;&amp;currentValue.contentId.length()>0"
 >
             <display />
         </field>
 
         <field name="contentId" position="1"
-            use-when="&quot;${currentValue.contentId}&quot;.length()==0" >
+            
use-when="currentValue.contentId==null||currentValue.contentId.length()==0" >
             <text />
         </field>
         <field name="templateDataResourceId">
@@ -483,7 +483,7 @@ under the License.
         </field>
         <field name="dataResourceId" 
title="${uiLabelMap.ContentDataResourceId}">
             <lookup target-form-name="LookupDataResource">
-            <sub-hyperlink 
use-when="&quot;${currentValue.dataResourceId}&quot;.length()>0" 
link-style="buttontext" target-type="intra-app" target="gotoDataResource" 
description="${uiLabelMap.ContentGoToDataResource}">
+            <sub-hyperlink 
use-when="currentValue.dataResourceId!=null&amp;&amp;currentValue.dataResourceId.length()>0"
 link-style="buttontext" target-type="intra-app" target="gotoDataResource" 
description="${uiLabelMap.ContentGoToDataResource}">
                 <parameter param-name="dataResourceId" 
from-field="currentValue.dataResourceId"/>
             </sub-hyperlink>
 <!--
diff --git 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/model/ModelFormFieldTest.java
 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/model/ModelFormFieldTest.java
index 1b98cabb90..a3a50189c9 100644
--- 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/model/ModelFormFieldTest.java
+++ 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/model/ModelFormFieldTest.java
@@ -22,6 +22,8 @@ import static 
org.apache.ofbiz.widget.model.ModelFormField.from;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
@@ -158,4 +160,77 @@ public final class ModelFormFieldTest {
                 ImmutableMap.of("prefix", "P1", "key1", "AA,BB , CC"));
         assertThat(targetParameterList, Matchers.contains("P1TargetParam", 
"AA", "BB", "CC"));
     }
+
+    /**
+     * Regression test for the CMS caContentIdTo/caMapKey use-when handling 
(applications/content/widget/cms/CMSForms.xml).
+     * <p>{@code shouldUse} expands the {@code use-when} attribute with {@link 
org.apache.ofbiz.base.util.string.FlexibleStringExpander}
+     * before handing it to Groovy: a {@code 
use-when="&quot;${var}&quot;.length()>0"} template textually splices the
+     * <em>stringified value</em> of a context variable into the source string 
that is then compiled and executed. If that
+     * context variable is populated from an HTTP request parameter, a value 
containing a closing quote can escape the
+     * string literal and reach the surrounding Groovy source, which -- as 
demonstrated separately via reflection
+     * (Class.forName("java.lang.Runtime") + Method.invoke) -- is not stopped 
by GroovyUtil's compile-time
+     * SecureASTCustomizer sandbox. The fix (applied after this test is 
written) is to stop templating the value into a
+     * string literal and instead reference the context variable directly, the 
same safe idiom already used by the vast
+     * majority of use-when expressions elsewhere in the codebase (e.g. 
"currentValue==null").
+     */
+    @Test
+    public void useWhenDoesNotExecuteContextValueAsGroovyCode() throws 
Exception {
+        // A synchronous, in-JVM side effect (no OS process spawn) so the test 
is deterministic: no race waiting
+        // on an async subprocess to finish writing a file. Running arbitrary 
code is proof enough here; a
+        // separate, manually-verified reproduction additionally shows the 
same string literal escape reaching
+        // Runtime.exec() via reflection (Class.forName("java.lang.Runtime") + 
Method.invoke), bypassing
+        // GroovyUtil's compile-time SecureASTCustomizer sandbox -- that 
sandbox is irrelevant to *this* test,
+        // since the sandbox was never the defense that mattered: nothing 
should be textually splicing untrusted
+        // data into compiled source.
+        String marker = "ofbiz.usewhen.test.marker";
+        System.clearProperty(marker);
+        try {
+            // Matches the fixed use-when now in CMSForms.xml: a direct, 
unquoted binding reference instead of a
+            // FlexibleStringExpander "${var}" template spliced into a string 
literal.
+            ModelFormField field = from(b -> 
b.setName("caContentIdTo").setUseWhen("caContentIdTo!=null&&caContentIdTo.length()>0"));
+
+            HashMap<String, Object> legitimate = new HashMap<>();
+            legitimate.put("caContentIdTo", "LEGITIMATE_ID");
+            assertTrue(field.shouldUse(legitimate), "A non-empty legitimate 
value must still satisfy the use-when");
+
+            HashMap<String, Object> empty = new HashMap<>();
+            assertFalse(field.shouldUse(empty), "A missing value must not 
satisfy the use-when");
+
+            HashMap<String, Object> untrusted = new HashMap<>();
+            untrusted.put("caContentIdTo", "x\";System.setProperty(\"" + 
marker + "\",\"SIDE_EFFECT\");\"x");
+            // The untrusted text is still non-empty, so the field is 
legitimately shown -- that part
+            // is expected and harmless. What must NOT happen is the embedded 
statement executing as code.
+            assertTrue(field.shouldUse(untrusted), "The untrusted value is 
still non-empty text, so use-when legitimately evaluates true");
+            assertFalse(System.getProperty(marker) != null, "The embedded 
Groovy statement must not execute as code");
+        } finally {
+            System.clearProperty(marker);
+        }
+    }
+
+    /**
+     * Same handling, same fix, for the sibling caMapKey use-when in the same 
CMSForms.xml form.
+     * See {@link #useWhenDoesNotExecuteContextValueAsGroovyCode()} for the 
full explanation.
+     */
+    @Test
+    public void useWhenDoesNotExecuteContextValueAsGroovyCodeForCaMapKey() 
throws Exception {
+        String marker = "ofbiz.usewhen.test.marker.camapkey";
+        System.clearProperty(marker);
+        try {
+            ModelFormField field = from(b -> 
b.setName("caMapKey").setUseWhen("caMapKey!=null&&caMapKey.length()>0"));
+
+            HashMap<String, Object> legitimate = new HashMap<>();
+            legitimate.put("caMapKey", "LEGITIMATE_KEY");
+            assertTrue(field.shouldUse(legitimate), "A non-empty legitimate 
value must still satisfy the use-when");
+
+            HashMap<String, Object> empty = new HashMap<>();
+            assertFalse(field.shouldUse(empty), "A missing value must not 
satisfy the use-when");
+
+            HashMap<String, Object> untrusted = new HashMap<>();
+            untrusted.put("caMapKey", "x\";System.setProperty(\"" + marker + 
"\",\"SIDE_EFFECT\");\"x");
+            assertTrue(field.shouldUse(untrusted), "The untrusted value is 
still non-empty text, so use-when legitimately evaluates true");
+            assertFalse(System.getProperty(marker) != null, "The embedded 
Groovy statement must not execute as code");
+        } finally {
+            System.clearProperty(marker);
+        }
+    }
 }

Reply via email to