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

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

commit 76f9a223e67397d4ee7aea935b2715437b7bbcf3
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Jul 10 09:01:05 2026 +0100

    Fixing policy intersection bug
---
 .../org/apache/neethi/AbstractPolicyOperator.java  |  8 +-
 .../org/apache/neethi/util/PolicyIntersector.java  |  6 +-
 .../apache/neethi/PolicyIntersectionDoSTest.java   | 94 ++++++++++++++++++++++
 3 files changed, 103 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java 
b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
index 491ee27..f55acbb 100644
--- a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
+++ b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
@@ -263,12 +263,16 @@ public abstract class AbstractPolicyOperator implements 
PolicyOperator {
         return crossProduct;
     }
 
-    private static void checkAlternativeBudget(long alternativesCount) {
+    public static void checkMaximumAlternativeCount(long alternativesCount, 
String operation) {
         if (alternativesCount > MAX_ALTERNATIVES) {
             throw new RuntimeException(
-                "Policy normalization exceeded the maximum number of 
alternatives ("
+                "Policy " + operation + " exceeded the maximum number of 
alternatives ("
                 + MAX_ALTERNATIVES + "). The policy may be crafted to cause "
                 + "Algorithmic Complexity DoS via exponential expansion.");
         }
     }
+
+    private static void checkAlternativeBudget(long alternativesCount) {
+        checkMaximumAlternativeCount(alternativesCount, "normalization");
+    }
 }
diff --git a/src/main/java/org/apache/neethi/util/PolicyIntersector.java 
b/src/main/java/org/apache/neethi/util/PolicyIntersector.java
index 67195ce..926aa86 100644
--- a/src/main/java/org/apache/neethi/util/PolicyIntersector.java
+++ b/src/main/java/org/apache/neethi/util/PolicyIntersector.java
@@ -24,6 +24,7 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
+import org.apache.neethi.AbstractPolicyOperator;
 import org.apache.neethi.All;
 import org.apache.neethi.Assertion;
 import org.apache.neethi.ExactlyOne;
@@ -185,9 +186,6 @@ public class PolicyIntersector {
     public Policy intersect(Policy p1, Policy p2, boolean allowDups) {
         Policy compatible = new Policy(p1.getPolicyRegistry(), 
p1.getNamespace());
         ExactlyOne eo = new ExactlyOne(compatible);
-        if (!compatiblePolicies(p1, p2)) {
-            return compatible;
-        }
         Iterator<List<Assertion>> i1 = p1.getAlternatives();
         while (i1.hasNext()) {
             List<Assertion> alt1 = i1.next();
@@ -196,6 +194,8 @@ public class PolicyIntersector {
                 List<Assertion> alt2 = i2.next();
                 All all = createCompatibleAlternatives(alt1, alt2, !allowDups);
                 if (all != null) {
+                    long nextSize = (long)eo.getPolicyComponents().size() + 1;
+                    
AbstractPolicyOperator.checkMaximumAlternativeCount(nextSize, "intersection");
                     eo.addPolicyComponent(all);
                 }
             }            
diff --git a/src/test/java/org/apache/neethi/PolicyIntersectionDoSTest.java 
b/src/test/java/org/apache/neethi/PolicyIntersectionDoSTest.java
new file mode 100644
index 0000000..7469496
--- /dev/null
+++ b/src/test/java/org/apache/neethi/PolicyIntersectionDoSTest.java
@@ -0,0 +1,94 @@
+/**
+ * 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.
+ */
+
+package org.apache.neethi;
+
+import javax.xml.namespace.QName;
+
+import org.apache.neethi.builders.PrimitiveAssertion;
+import org.junit.Test;
+
+public class PolicyIntersectionDoSTest extends PolicyTestCase {
+
+    private static final int ALTERNATIVES_PER_POLICY = 101;
+    private static final int SAFE_ALTERNATIVES_PER_POLICY = 100;
+
+    @Test
+    public void 
testIntersectionRejectsCartesianProductBeyondAlternativeBudget() {
+        Policy left = 
buildPolicyWithEmptyAlternatives(ALTERNATIVES_PER_POLICY);
+        Policy right = 
buildPolicyWithEmptyAlternatives(ALTERNATIVES_PER_POLICY);
+
+        try {
+            left.intersect(right, true);
+            fail("Expected RuntimeException due to intersection alternative 
limit");
+        } catch (RuntimeException ex) {
+            assertTrue(ex.getMessage().contains("intersection"));
+            assertTrue(ex.getMessage().contains("maximum number of 
alternatives"));
+        }
+    }
+
+    @Test
+    public void testIntersectionAllowsCartesianProductAtAlternativeBudget() {
+        Policy left = 
buildPolicyWithEmptyAlternatives(SAFE_ALTERNATIVES_PER_POLICY);
+        Policy right = 
buildPolicyWithEmptyAlternatives(SAFE_ALTERNATIVES_PER_POLICY);
+
+        Policy intersection = left.intersect(right, true);
+
+        assertNotNull(intersection);
+        assertEquals(
+            SAFE_ALTERNATIVES_PER_POLICY * SAFE_ALTERNATIVES_PER_POLICY,
+            
((ExactlyOne)intersection.getFirstPolicyComponent()).getPolicyComponents().size());
+    }
+
+    @Test
+    public void 
testIntersectionWithNoCompatibleAlternativesReturnsEmptyPolicy() {
+        Policy left = buildSingleAssertionPolicy("left");
+        Policy right = buildSingleAssertionPolicy("right");
+
+        Policy intersection = left.intersect(right, true);
+
+        assertNotNull(intersection);
+        assertTrue(intersection.getFirstPolicyComponent() instanceof 
ExactlyOne);
+        
assertTrue(((ExactlyOne)intersection.getFirstPolicyComponent()).getPolicyComponents().isEmpty());
+    }
+
+    private static Policy buildPolicyWithEmptyAlternatives(int alternatives) {
+        Policy policy = new Policy();
+        ExactlyOne exactlyOne = new ExactlyOne();
+
+        for (int i = 0; i < alternatives; i++) {
+            exactlyOne.addPolicyComponent(new All());
+        }
+
+        policy.addPolicyComponent(exactlyOne);
+        return policy;
+    }
+
+    private static Policy buildSingleAssertionPolicy(String localName) {
+        Policy policy = new Policy();
+        ExactlyOne exactlyOne = new ExactlyOne();
+        All all = new All();
+
+        all.addPolicyComponent(new PrimitiveAssertion(new QName("urn:test", 
localName)));
+        exactlyOne.addPolicyComponent(all);
+        policy.addPolicyComponent(exactlyOne);
+
+        return policy;
+    }
+}
\ No newline at end of file

Reply via email to