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 <[email protected]>
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"));
+        }
+    }
 }

Reply via email to