You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2026/04/21 09:23:24 UTC

(ws-neethi) branch master updated (bcff4ec -> 7a38f2b)

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

coheigea pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git


    from bcff4ec  Fixing entity reference parsing
     new 7cf2056  Adding StaxToDOMConverter unit tests
     new 7a38f2b  Detect circular policy references

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/neethi/AbstractPolicyOperator.java  |  21 ++-
 .../org/apache/neethi/PolicyReferenceTest.java     |  49 +++++
 .../converters/StaxToDOMConverterTest.java         | 204 ++++++++++++++++++++-
 3 files changed, 259 insertions(+), 15 deletions(-)


(ws-neethi) 02/02: Detect circular policy references

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git

commit 7a38f2be139d70fbf879fbb54eebe1fde633cc32
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Tue Apr 21 10:22:32 2026 +0100

    Detect circular policy references
---
 .../org/apache/neethi/AbstractPolicyOperator.java  | 21 +++++++---
 .../org/apache/neethi/PolicyReferenceTest.java     | 49 ++++++++++++++++++++++
 2 files changed, 64 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
index b9064e0..015c8b2 100644
--- a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
+++ b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
@@ -20,8 +20,10 @@
 package org.apache.neethi;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.neethi.util.PolicyComparator;
 
@@ -84,14 +86,15 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
         }
         
         
-        result.addPolicyComponent(normalizeOperator(policy, policy, reg, deep));
+        result.addPolicyComponent(normalizeOperator(policy, policy, reg, deep, new HashSet<String>()));
         return result;
     }
     
     private static PolicyComponent normalizeOperator(Policy policy, 
                                                      PolicyOperator operator, 
                                                      PolicyRegistry reg,
-                                                     boolean deep) {
+                                                     boolean deep,
+                                                     Set<String> resolving) {
                         
         short type = operator.getType();
                 
@@ -142,20 +145,26 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
                 if (policyComponent == null) {
                     throw new RuntimeException(uri + " can't be resolved");
                 }
-                
+                String resolvedId = ((Policy) policyComponent).getId();
+                if (resolvedId != null && !resolving.add(resolvedId)) {
+                    throw new RuntimeException("Circular PolicyReference detected: " + resolvedId);
+                }
                 All all = new All();
                 all.addPolicyComponents(((Policy) policyComponent).getPolicyComponents());
-                childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep));
+                childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, resolving));
+                if (resolvedId != null) {
+                    resolving.remove(resolvedId);
+                }
          
             } else if (policyComponent.getType() == Constants.TYPE_POLICY) {
                 All all = new All();
                 all.addPolicyComponents(((Policy) policyComponent).getPolicyComponents());
-                childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep));
+                childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, resolving));
                 
             } else {
                 childComponentsList.add(AbstractPolicyOperator
                                             .normalizeOperator(policy,
-                                                               (PolicyOperator)policyComponent, reg, deep));
+                                                               (PolicyOperator)policyComponent, reg, deep, resolving));
             }            
         }
         
diff --git a/src/test/java/org/apache/neethi/PolicyReferenceTest.java b/src/test/java/org/apache/neethi/PolicyReferenceTest.java
index 6633264..e0d0c46 100644
--- a/src/test/java/org/apache/neethi/PolicyReferenceTest.java
+++ b/src/test/java/org/apache/neethi/PolicyReferenceTest.java
@@ -162,4 +162,53 @@ public class PolicyReferenceTest extends PolicyTestCase {
                 e.getMessage().contains("Unsupported URI scheme"));
         }
     }
+
+    /**
+     * Test two policies that mutually reference each other cause unbounded recursion in
+     * AbstractPolicyOperator.normalizeOperator() when normalize(true) is called.
+     *
+     * Policy A contains a PolicyReference to "B".
+     * Policy B contains a PolicyReference to "A".
+     * Both are registered in the PolicyRegistry before normalization begins.
+     *
+     * normalizeOperator() resolves each PolicyReference via reg.lookup() and
+     * recurses into the resolved policy's components with no cycle detection,
+     * ultimately throwing StackOverflowError.
+     */
+    @Test
+    public void testCircularPolicyReferenceThrowsStackOverflow() {
+        // Build Policy A: ExactlyOne > All > PolicyReference("B")
+        PolicyReference refToB = new PolicyReference();
+        refToB.setURI("B");
+        All allA = new All();
+        allA.addPolicyComponent(refToB);
+        ExactlyOne eoA = new ExactlyOne();
+        eoA.addPolicyComponent(allA);
+        Policy policyA = new Policy(registry);
+        policyA.setId("A");
+        policyA.addPolicyComponent(eoA);
+
+        // Build Policy B: ExactlyOne > All > PolicyReference("A")
+        PolicyReference refToA = new PolicyReference();
+        refToA.setURI("A");
+        All allB = new All();
+        allB.addPolicyComponent(refToA);
+        ExactlyOne eoB = new ExactlyOne();
+        eoB.addPolicyComponent(allB);
+        Policy policyB = new Policy(registry);
+        policyB.setId("B");
+        policyB.addPolicyComponent(eoB);
+
+        registry.register("A", policyA);
+        registry.register("B", policyB);
+
+        try {
+            policyA.normalize(registry, true);
+            fail("Expected StackOverflowError or RuntimeException due to circular reference");
+        } catch (RuntimeException e) {
+            assertTrue(
+                "Expected a cycle-detection message but got: " + e.getMessage(),
+                e.getMessage() != null && e.getMessage().toLowerCase().contains("circular"));
+        }
+    }
 }


(ws-neethi) 01/02: Adding StaxToDOMConverter unit tests

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git

commit 7cf2056e8a200bd43d614490211909b5d6353c46
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Tue Apr 21 10:14:30 2026 +0100

    Adding StaxToDOMConverter unit tests
---
 .../converters/StaxToDOMConverterTest.java         | 204 ++++++++++++++++++++-
 1 file changed, 195 insertions(+), 9 deletions(-)

diff --git a/src/test/java/org/apache/neethi/builders/converters/StaxToDOMConverterTest.java b/src/test/java/org/apache/neethi/builders/converters/StaxToDOMConverterTest.java
index 25bcfd8..8c51e46 100644
--- a/src/test/java/org/apache/neethi/builders/converters/StaxToDOMConverterTest.java
+++ b/src/test/java/org/apache/neethi/builders/converters/StaxToDOMConverterTest.java
@@ -21,6 +21,7 @@ package org.apache.neethi.builders.converters;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 import java.io.StringReader;
 
@@ -28,15 +29,205 @@ import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamReader;
 
 import org.junit.Test;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
 
 public class StaxToDOMConverterTest {
 
+    // -----------------------------------------------------------------------
+    // Helpers
+    // -----------------------------------------------------------------------
+
+    private static XMLStreamReader readerFor(String xml) throws Exception {
+        XMLInputFactory xif = XMLInputFactory.newInstance();
+        xif.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE);
+        return xif.createXMLStreamReader(new StringReader(xml));
+    }
+
+    private static Element convert(String xml) throws Exception {
+        return new StaxToDOMConverter().convert(readerFor(xml));
+    }
+
+    // -----------------------------------------------------------------------
+    // Element structure
+    // -----------------------------------------------------------------------
+
+    @Test
+    public void testSimpleElement() throws Exception {
+        Element el = convert("<root/>");
+        assertNotNull(el);
+        assertEquals("root", el.getLocalName());
+        assertNull(el.getFirstChild());
+    }
+
+    @Test
+    public void testNestedElements() throws Exception {
+        Element root = convert("<root><child><grandchild/></child></root>");
+        assertEquals("root", root.getLocalName());
+        Element child = (Element) root.getFirstChild();
+        assertEquals("child", child.getLocalName());
+        Element grandchild = (Element) child.getFirstChild();
+        assertEquals("grandchild", grandchild.getLocalName());
+        assertNull(grandchild.getFirstChild());
+    }
+
+    @Test
+    public void testMultipleSiblings() throws Exception {
+        Element root = convert("<root><a/><b/><c/></root>");
+        Node a = root.getFirstChild();
+        assertEquals("a", a.getLocalName());
+        Node b = a.getNextSibling();
+        assertEquals("b", b.getLocalName());
+        Node c = b.getNextSibling();
+        assertEquals("c", c.getLocalName());
+        assertNull(c.getNextSibling());
+    }
+
+    // -----------------------------------------------------------------------
+    // Attributes
+    // -----------------------------------------------------------------------
+
+    @Test
+    public void testUnprefixedAttribute() throws Exception {
+        Element el = convert("<root foo=\"bar\"/>");
+        assertEquals("bar", el.getAttribute("foo"));
+    }
+
+    @Test
+    public void testMultipleAttributes() throws Exception {
+        Element el = convert("<root a=\"1\" b=\"2\" c=\"3\"/>");
+        assertEquals("1", el.getAttribute("a"));
+        assertEquals("2", el.getAttribute("b"));
+        assertEquals("3", el.getAttribute("c"));
+    }
+
+    // -----------------------------------------------------------------------
+    // Namespaces
+    // -----------------------------------------------------------------------
+
+    @Test
+    public void testNamespacedElement() throws Exception {
+        Element el = convert("<ns:root xmlns:ns=\"http://example.com/\"/>");
+        assertEquals("http://example.com/", el.getNamespaceURI());
+        assertEquals("root", el.getLocalName());
+        assertEquals("ns", el.getPrefix());
+    }
+
+    @Test
+    public void testDefaultNamespace() throws Exception {
+        Element el = convert("<root xmlns=\"http://example.com/\"/>");
+        assertEquals("http://example.com/", el.getNamespaceURI());
+        assertEquals("root", el.getLocalName());
+    }
+
+    @Test
+    public void testNamespacedAttribute() throws Exception {
+        Element el = convert(
+            "<root xmlns:x=\"http://example.com/\" x:attr=\"val\"/>");
+        assertEquals("val", el.getAttributeNS("http://example.com/", "attr"));
+    }
+
+    @Test
+    public void testChildInheritedNamespace() throws Exception {
+        Element root = convert(
+            "<ns:root xmlns:ns=\"http://example.com/\"><ns:child/></ns:root>");
+        Element child = (Element) root.getFirstChild();
+        assertEquals("http://example.com/", child.getNamespaceURI());
+        assertEquals("child", child.getLocalName());
+    }
+
+    // -----------------------------------------------------------------------
+    // Text content
+    // -----------------------------------------------------------------------
+
+    @Test
+    public void testTextContent() throws Exception {
+        Element el = convert("<root>hello world</root>");
+        Text text = (Text) el.getFirstChild();
+        assertEquals("hello world", text.getNodeValue());
+    }
+
+    @Test
+    public void testMixedContent() throws Exception {
+        Element root = convert("<root>before<child/>after</root>");
+        Node before = root.getFirstChild();
+        assertEquals(Node.TEXT_NODE, before.getNodeType());
+        assertEquals("before", before.getNodeValue());
+        Node child = before.getNextSibling();
+        assertEquals("child", child.getLocalName());
+        Node after = child.getNextSibling();
+        assertEquals(Node.TEXT_NODE, after.getNodeType());
+        assertEquals("after", after.getNodeValue());
+    }
+
+    // -----------------------------------------------------------------------
+    // Comment
+    // -----------------------------------------------------------------------
+
+    @Test
+    public void testComment() throws Exception {
+        Element root = convert("<root><!-- a comment --></root>");
+        Comment comment = (Comment) root.getFirstChild();
+        assertEquals(" a comment ", comment.getData());
+    }
+
+    @Test
+    public void testCommentAmongSiblings() throws Exception {
+        Element root = convert("<root><a/><!-- note --><b/></root>");
+        Node a = root.getFirstChild();
+        assertEquals("a", a.getLocalName());
+        Node comment = a.getNextSibling();
+        assertEquals(Node.COMMENT_NODE, comment.getNodeType());
+        assertEquals(" note ", comment.getNodeValue());
+        Node b = comment.getNextSibling();
+        assertEquals("b", b.getLocalName());
+    }
+
+    // -----------------------------------------------------------------------
+    // CDATA section
+    // -----------------------------------------------------------------------
+
+    @Test
+    public void testCDataSection() throws Exception {
+        Element root = convert("<root><![CDATA[some <raw> data]]></root>");
+        CDATASection cdata = (CDATASection) root.getFirstChild();
+        assertEquals("some <raw> data", cdata.getData());
+    }
+
+    // -----------------------------------------------------------------------
+    // Processing instruction
+    // -----------------------------------------------------------------------
+
+    @Test
+    public void testProcessingInstruction() throws Exception {
+        Element root = convert("<root><?myapp key=\"value\"?></root>");
+        ProcessingInstruction pi = (ProcessingInstruction) root.getFirstChild();
+        assertEquals("myapp", pi.getTarget());
+        assertEquals("key=\"value\"", pi.getData());
+    }
+
+    @Test
+    public void testProcessingInstructionNoData() throws Exception {
+        Element root = convert("<root><?myapp?></root>");
+        ProcessingInstruction pi = (ProcessingInstruction) root.getFirstChild();
+        assertEquals("myapp", pi.getTarget());
+    }
+
+    // -----------------------------------------------------------------------
+    // Entity reference (Finding 6 — CWE-617)
+    // -----------------------------------------------------------------------
+
     /**
-     * Converts an XML document containing an entity reference with
-     * IS_REPLACING_ENTITY_REFERENCES=false, which causes the StAX parser to emit
-     * an ENTITY_REFERENCE event instead of expanding the entity inline.
+     * Reproduces Finding 6 (SECURITY_REPORT.md): on the unfixed code the
+     * ENTITY_REFERENCE case called createProcessingInstruction(getPITarget(),
+     * getPIData()), where getPITarget() throws IllegalStateException because the
+     * current event is not PROCESSING_INSTRUCTION.
+     *
+     * The fix changes it to createEntityReference(reader.getLocalName()).
      */
     @Test
     public void testEntityReferenceProducesEntityReferenceNode() throws Exception {
@@ -45,22 +236,17 @@ public class StaxToDOMConverterTest {
             + "<root>&greet;</root>";
 
         XMLInputFactory xif = XMLInputFactory.newInstance();
-        // Prevent the parser from expanding &greet; into text, so it emits
-        // an ENTITY_REFERENCE event that exercises the buggy case.
         xif.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE);
         xif.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.TRUE);
 
         XMLStreamReader reader = xif.createXMLStreamReader(new StringReader(xml));
-
         Element element = new StaxToDOMConverter().convert(reader);
 
         assertNotNull(element);
         Node child = element.getFirstChild();
         assertNotNull("Expected an entity reference child node under <root>", child);
         assertEquals(
-            "Expected ENTITY_REFERENCE_NODE (" + Node.ENTITY_REFERENCE_NODE + ") "
-                + "but got node type " + child.getNodeType()
-                + " — ENTITY_REFERENCE case calls createProcessingInstruction instead of createEntityReference",
+            "Expected ENTITY_REFERENCE_NODE but got node type " + child.getNodeType(),
             Node.ENTITY_REFERENCE_NODE,
             child.getNodeType());
         assertEquals("greet", child.getNodeName());