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

cshannon pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq.git


The following commit(s) were added to refs/heads/main by this push:
     new c8b71abe2f Optimize SecurityContext#isInOneOf attempt 2 (#1879)
c8b71abe2f is described below

commit c8b71abe2f9f9a3b8643b0d888623053e37e9fb2
Author: Christopher L. Shannon <[email protected]>
AuthorDate: Mon Apr 6 15:28:05 2026 -0400

    Optimize SecurityContext#isInOneOf attempt 2 (#1879)
    
    This is an update to #1874 which fixes the previous issue with wildcard
    principal handling which caused things to break
    
    This method was iterating over a set to find matching entries instead of
    just using contains(). This update switches to using contains() which will
    be faster and also cleans up the code with a for each loop.
    
    The wildcard principal handling was simplified by using a
    predefined static object inside SecurityContext to mark
    that everything should be allowed if the name is "*".
    DefaultAuthorizationMap now references this and uses this object
    when parsing ACLs if a wildcard is used. Because it's been moved
    to SecurityContext it's flexible and can be used by other impls as well.
    
    A small test was added but there are several other authorization tests that
    already exist that also exercise this method.
---
 .../activemq/security/DefaultAuthorizationMap.java |  56 +++--------
 .../apache/activemq/security/SecurityContext.java  |  39 ++++++--
 .../activemq/security/SecurityContextTest.java     | 111 +++++++++++++++++++++
 .../security/SimpleSecurityBrokerSystemTest.java   |   3 +-
 4 files changed, 157 insertions(+), 52 deletions(-)

diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java
 
b/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java
index 290f6bc07b..2c95c3740e 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java
@@ -16,9 +16,11 @@
  */
 package org.apache.activemq.security;
 
+import static org.apache.activemq.security.SecurityContext.WILDCARD_PRINCIPAL;
+import static org.apache.activemq.security.SecurityContext.isWildcardPrincipal;
+
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
-import java.security.Principal;
 import java.util.*;
 
 import org.apache.activemq.command.ActiveMQDestination;
@@ -99,8 +101,7 @@ public class DefaultAuthorizationMap extends DestinationMap 
implements Authoriza
         Set<Object> answer = new WildcardAwareSet<Object>();
 
         // now lets go through each entry adding individual
-        for (Iterator<AuthorizationEntry> iter = entries.iterator(); 
iter.hasNext();) {
-            AuthorizationEntry entry = iter.next();
+        for (AuthorizationEntry entry : entries) {
             answer.addAll(entry.getAdminACLs());
         }
         return answer;
@@ -112,8 +113,7 @@ public class DefaultAuthorizationMap extends DestinationMap 
implements Authoriza
         Set<Object> answer = new WildcardAwareSet<Object>();
 
         // now lets go through each entry adding individual
-        for (Iterator<AuthorizationEntry> iter = entries.iterator(); 
iter.hasNext();) {
-            AuthorizationEntry entry = iter.next();
+        for (AuthorizationEntry entry : entries) {
             answer.addAll(entry.getReadACLs());
         }
         return answer;
@@ -125,8 +125,7 @@ public class DefaultAuthorizationMap extends DestinationMap 
implements Authoriza
         Set<Object> answer = new WildcardAwareSet<Object>();
 
         // now lets go through each entry adding individual
-        for (Iterator<AuthorizationEntry> iter = entries.iterator(); 
iter.hasNext();) {
-            AuthorizationEntry entry = iter.next();
+        for (AuthorizationEntry entry : entries) {
             answer.addAll(entry.getWriteACLs());
         }
         return answer;
@@ -157,10 +156,9 @@ public class DefaultAuthorizationMap extends 
DestinationMap implements Authoriza
         if (key.isComposite()) {
             ActiveMQDestination[] destinations = 
key.getCompositeDestinations();
             Set answer = null;
-            for (int i = 0; i < destinations.length; i++) {
-                ActiveMQDestination childDestination = destinations[i];
+            for (ActiveMQDestination childDestination : destinations) {
                 answer = union(answer, get(childDestination));
-                if (answer == null  || answer.isEmpty()) {
+                if (answer == null || answer.isEmpty()) {
                     break;
                 }
             }
@@ -210,26 +208,14 @@ public class DefaultAuthorizationMap extends 
DestinationMap implements Authoriza
         this.groupClass = groupClass;
     }
 
-    final static String WILDCARD = "*";
+    static final String WILDCARD = SecurityContext.WILDCARD;
+
     public static Object createGroupPrincipal(String name, String groupClass) 
throws Exception {
         if (WILDCARD.equals(name)) {
             // simple match all group principal - match any name and class
-            return new Principal() {
-                @Override
-                public String getName() {
-                    return WILDCARD;
-                }
-                @Override
-                public boolean equals(Object other) {
-                    return true;
-                }
-
-                @Override
-                public int hashCode() {
-                    return WILDCARD.hashCode();
-                }
-            };
+            return WILDCARD_PRINCIPAL;
         }
+
         Object[] param = new Object[]{name};
 
         Class<?> cls = Class.forName(groupClass);
@@ -266,7 +252,7 @@ public class DefaultAuthorizationMap extends DestinationMap 
implements Authoriza
         return instance;
     }
 
-    class WildcardAwareSet<T> extends HashSet<T> {
+    static class WildcardAwareSet<T> extends HashSet<T> {
         boolean hasWildcard = false;
 
         @Override
@@ -281,10 +267,8 @@ public class DefaultAuthorizationMap extends 
DestinationMap implements Authoriza
         @Override
         public boolean addAll(Collection<? extends T> collection) {
             boolean modified = false;
-            Iterator<? extends T> e = collection.iterator();
-            while (e.hasNext()) {
-                final T item = e.next();
-                if (isWildcard(item)) {
+            for (T item : collection) {
+                if (isWildcardPrincipal(item)) {
                     hasWildcard = true;
                 }
                 if (add(item)) {
@@ -293,15 +277,5 @@ public class DefaultAuthorizationMap extends 
DestinationMap implements Authoriza
             }
             return modified;
         }
-
-        private boolean isWildcard(T item) {
-            try {
-                if (item.getClass().getMethod("getName", new 
Class[]{}).invoke(item).equals("*")) {
-                    return true;
-                }
-            } catch (Exception ignored) {
-            }
-            return false;
-        }
     }
 }
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/security/SecurityContext.java
 
b/activemq-broker/src/main/java/org/apache/activemq/security/SecurityContext.java
index fd677ce590..39835117fd 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/security/SecurityContext.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/security/SecurityContext.java
@@ -16,10 +16,9 @@
  */
 package org.apache.activemq.security;
 
+
 import java.security.Principal;
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -33,6 +32,10 @@ import org.apache.activemq.command.ActiveMQDestination;
  */
 public abstract class SecurityContext {
 
+    static final String WILDCARD = "*";
+
+    static final Principal WILDCARD_PRINCIPAL = () -> WILDCARD;
+
     public static final SecurityContext BROKER_SECURITY_CONTEXT = new 
SecurityContext("ActiveMQBroker") {
         @Override
         public boolean isBrokerContext() {
@@ -53,15 +56,26 @@ public abstract class SecurityContext {
         this.userName = userName;
     }
 
+    public static boolean isWildcardPrincipal(Object principal) {
+        return principal == WILDCARD_PRINCIPAL;
+    }
+
+    public static boolean containsWildcardPrincipal(Set<?> principals) {
+        return principals.contains(WILDCARD_PRINCIPAL);
+    }
+
     public boolean isInOneOf(Set<?> allowedPrincipals) {
-        Iterator<?> allowedIter = allowedPrincipals.iterator();
-        HashSet<?> userPrincipals = new HashSet<Object>(getPrincipals());
-        while (allowedIter.hasNext()) {
-            Iterator<?> userIter = userPrincipals.iterator();
-            Object allowedPrincipal = allowedIter.next();
-            while (userIter.hasNext()) {
-                if (allowedPrincipal.equals(userIter.next()))
-                    return true;
+        // Wildcard Principal is a special principal that allows all
+        // Check for that first using an identify check. This is
+        // a fast check that allows us to avoid iterating if true
+        if (containsWildcardPrincipal(allowedPrincipals)) {
+            return true;
+        }
+
+        // Iterate over all the principals to see if there are matches
+        for (Object allowedPrincipal : allowedPrincipals) {
+            if (contains(allowedPrincipal)) {
+                return true;
             }
         }
         return false;
@@ -69,6 +83,11 @@ public abstract class SecurityContext {
 
     public abstract Set<Principal> getPrincipals();
 
+    public boolean contains(Object principal) {
+        Set<Principal> principals = getPrincipals();
+        return principals != null && principals.contains(principal);
+    }
+
     public String getUserName() {
         return userName;
     }
diff --git 
a/activemq-broker/src/test/java/org/apache/activemq/security/SecurityContextTest.java
 
b/activemq-broker/src/test/java/org/apache/activemq/security/SecurityContextTest.java
new file mode 100644
index 0000000000..471888715b
--- /dev/null
+++ 
b/activemq-broker/src/test/java/org/apache/activemq/security/SecurityContextTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.activemq.security;
+
+import static 
org.apache.activemq.security.SecurityContextTest.TestPrincipal.testPrincipal;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.security.Principal;
+import java.util.Objects;
+import java.util.Set;
+import org.junit.Test;
+
+public class SecurityContextTest {
+
+    @Test
+    public void testIsOneOf() {
+        SecurityContext context = newContext(Set.of(testPrincipal("one"),
+                testPrincipal("two"), testPrincipal("three")));
+
+        // test valid combos
+        assertTrue(context.isInOneOf(Set.of(testPrincipal("one"))));
+        assertTrue(context.isInOneOf(Set.of(testPrincipal("two"))));
+        assertTrue(context.isInOneOf(Set.of(testPrincipal("three"))));
+        assertTrue(context.isInOneOf(Set.of(testPrincipal("three"),
+                testPrincipal("four"), testPrincipal("five"))));
+
+        // test no matching
+        assertFalse(context.isInOneOf(Set.of(testPrincipal("four"),
+                testPrincipal("five"))));
+        assertFalse(context.isInOneOf(Set.of()));
+        // different impl types, should not find
+        assertFalse(context.isInOneOf(Set.of((Principal) () -> "one")));
+
+        // empty set
+        context = newContext(Set.of());
+        assertFalse(context.isInOneOf(Set.of(testPrincipal("one"))));
+        assertFalse(context.isInOneOf(Set.of()));
+    }
+
+    @Test
+    public void testWildcard() throws Exception {
+        SecurityContext context = newContext(Set.of(testPrincipal("one"),
+                testPrincipal("two"), testPrincipal("three")));
+
+        // test valid combos
+        Object wildcard1 = DefaultAuthorizationMap.createGroupPrincipal("*", 
"someIgnoredClass1");
+        Object wildcard2 = DefaultAuthorizationMap.createGroupPrincipal("*", 
"someIgnoredClass2");
+
+        // should be identical objects
+        assertSame(wildcard1, wildcard2);
+        // wildcard matches anything
+        assertTrue(context.isInOneOf(Set.of(wildcard1)));
+    }
+
+    private SecurityContext newContext(Set<Principal> principals) {
+        return new SecurityContext("user") {
+            @Override
+            public Set<Principal> getPrincipals() {
+                return principals;
+            }
+        };
+    }
+
+    static class TestPrincipal implements Principal {
+        private final String name;
+
+        private TestPrincipal(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            TestPrincipal that = (TestPrincipal) o;
+            return Objects.equals(name, that.name);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(name);
+        }
+
+        static TestPrincipal testPrincipal(String name) {
+            return new TestPrincipal(name);
+        }
+    }
+
+}
diff --git 
a/activemq-unit-tests/src/test/java/org/apache/activemq/security/SimpleSecurityBrokerSystemTest.java
 
b/activemq-unit-tests/src/test/java/org/apache/activemq/security/SimpleSecurityBrokerSystemTest.java
index bee67c372a..d87902add3 100644
--- 
a/activemq-unit-tests/src/test/java/org/apache/activemq/security/SimpleSecurityBrokerSystemTest.java
+++ 
b/activemq-unit-tests/src/test/java/org/apache/activemq/security/SimpleSecurityBrokerSystemTest.java
@@ -61,7 +61,8 @@ public class SimpleSecurityBrokerSystemTest extends 
SecurityTestSupport {
     public static Principal WILDCARD;
     static {
         try {
-         WILDCARD = (Principal) 
DefaultAuthorizationMap.createGroupPrincipal("*", 
GroupPrincipal.class.getName());
+         WILDCARD = (Principal) DefaultAuthorizationMap.createGroupPrincipal(
+                 SecurityContext.WILDCARD, GroupPrincipal.class.getName());
         } catch (Exception e) {
             LOG.error("Failed to make wildcard principal", e);
         }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information, visit: https://activemq.apache.org/contact


Reply via email to