Skip to content

Commit c8b71ab

Browse files
authored
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.
1 parent 656b881 commit c8b71ab

File tree

4 files changed

+157
-52
lines changed

4 files changed

+157
-52
lines changed

activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java

Lines changed: 15 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
*/
1717
package org.apache.activemq.security;
1818

19+
import static org.apache.activemq.security.SecurityContext.WILDCARD_PRINCIPAL;
20+
import static org.apache.activemq.security.SecurityContext.isWildcardPrincipal;
21+
1922
import java.lang.reflect.Constructor;
2023
import java.lang.reflect.Method;
21-
import java.security.Principal;
2224
import java.util.*;
2325

2426
import org.apache.activemq.command.ActiveMQDestination;
@@ -99,8 +101,7 @@ public Set<Object> getAdminACLs(ActiveMQDestination destination) {
99101
Set<Object> answer = new WildcardAwareSet<Object>();
100102

101103
// now lets go through each entry adding individual
102-
for (Iterator<AuthorizationEntry> iter = entries.iterator(); iter.hasNext();) {
103-
AuthorizationEntry entry = iter.next();
104+
for (AuthorizationEntry entry : entries) {
104105
answer.addAll(entry.getAdminACLs());
105106
}
106107
return answer;
@@ -112,8 +113,7 @@ public Set<Object> getReadACLs(ActiveMQDestination destination) {
112113
Set<Object> answer = new WildcardAwareSet<Object>();
113114

114115
// now lets go through each entry adding individual
115-
for (Iterator<AuthorizationEntry> iter = entries.iterator(); iter.hasNext();) {
116-
AuthorizationEntry entry = iter.next();
116+
for (AuthorizationEntry entry : entries) {
117117
answer.addAll(entry.getReadACLs());
118118
}
119119
return answer;
@@ -125,8 +125,7 @@ public Set<Object> getWriteACLs(ActiveMQDestination destination) {
125125
Set<Object> answer = new WildcardAwareSet<Object>();
126126

127127
// now lets go through each entry adding individual
128-
for (Iterator<AuthorizationEntry> iter = entries.iterator(); iter.hasNext();) {
129-
AuthorizationEntry entry = iter.next();
128+
for (AuthorizationEntry entry : entries) {
130129
answer.addAll(entry.getWriteACLs());
131130
}
132131
return answer;
@@ -157,10 +156,9 @@ public synchronized Set get(ActiveMQDestination key) {
157156
if (key.isComposite()) {
158157
ActiveMQDestination[] destinations = key.getCompositeDestinations();
159158
Set answer = null;
160-
for (int i = 0; i < destinations.length; i++) {
161-
ActiveMQDestination childDestination = destinations[i];
159+
for (ActiveMQDestination childDestination : destinations) {
162160
answer = union(answer, get(childDestination));
163-
if (answer == null || answer.isEmpty()) {
161+
if (answer == null || answer.isEmpty()) {
164162
break;
165163
}
166164
}
@@ -210,26 +208,14 @@ public void setGroupClass(String groupClass) {
210208
this.groupClass = groupClass;
211209
}
212210

213-
final static String WILDCARD = "*";
211+
static final String WILDCARD = SecurityContext.WILDCARD;
212+
214213
public static Object createGroupPrincipal(String name, String groupClass) throws Exception {
215214
if (WILDCARD.equals(name)) {
216215
// simple match all group principal - match any name and class
217-
return new Principal() {
218-
@Override
219-
public String getName() {
220-
return WILDCARD;
221-
}
222-
@Override
223-
public boolean equals(Object other) {
224-
return true;
225-
}
226-
227-
@Override
228-
public int hashCode() {
229-
return WILDCARD.hashCode();
230-
}
231-
};
216+
return WILDCARD_PRINCIPAL;
232217
}
218+
233219
Object[] param = new Object[]{name};
234220

235221
Class<?> cls = Class.forName(groupClass);
@@ -266,7 +252,7 @@ public int hashCode() {
266252
return instance;
267253
}
268254

269-
class WildcardAwareSet<T> extends HashSet<T> {
255+
static class WildcardAwareSet<T> extends HashSet<T> {
270256
boolean hasWildcard = false;
271257

272258
@Override
@@ -281,10 +267,8 @@ public boolean contains(Object e) {
281267
@Override
282268
public boolean addAll(Collection<? extends T> collection) {
283269
boolean modified = false;
284-
Iterator<? extends T> e = collection.iterator();
285-
while (e.hasNext()) {
286-
final T item = e.next();
287-
if (isWildcard(item)) {
270+
for (T item : collection) {
271+
if (isWildcardPrincipal(item)) {
288272
hasWildcard = true;
289273
}
290274
if (add(item)) {
@@ -293,15 +277,5 @@ public boolean addAll(Collection<? extends T> collection) {
293277
}
294278
return modified;
295279
}
296-
297-
private boolean isWildcard(T item) {
298-
try {
299-
if (item.getClass().getMethod("getName", new Class[]{}).invoke(item).equals("*")) {
300-
return true;
301-
}
302-
} catch (Exception ignored) {
303-
}
304-
return false;
305-
}
306280
}
307281
}

activemq-broker/src/main/java/org/apache/activemq/security/SecurityContext.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616
*/
1717
package org.apache.activemq.security;
1818

19+
1920
import java.security.Principal;
2021
import java.util.Collections;
21-
import java.util.HashSet;
22-
import java.util.Iterator;
2322
import java.util.Set;
2423
import java.util.concurrent.ConcurrentHashMap;
2524
import java.util.concurrent.ConcurrentMap;
@@ -33,6 +32,10 @@
3332
*/
3433
public abstract class SecurityContext {
3534

35+
static final String WILDCARD = "*";
36+
37+
static final Principal WILDCARD_PRINCIPAL = () -> WILDCARD;
38+
3639
public static final SecurityContext BROKER_SECURITY_CONTEXT = new SecurityContext("ActiveMQBroker") {
3740
@Override
3841
public boolean isBrokerContext() {
@@ -53,22 +56,38 @@ public SecurityContext(String userName) {
5356
this.userName = userName;
5457
}
5558

59+
public static boolean isWildcardPrincipal(Object principal) {
60+
return principal == WILDCARD_PRINCIPAL;
61+
}
62+
63+
public static boolean containsWildcardPrincipal(Set<?> principals) {
64+
return principals.contains(WILDCARD_PRINCIPAL);
65+
}
66+
5667
public boolean isInOneOf(Set<?> allowedPrincipals) {
57-
Iterator<?> allowedIter = allowedPrincipals.iterator();
58-
HashSet<?> userPrincipals = new HashSet<Object>(getPrincipals());
59-
while (allowedIter.hasNext()) {
60-
Iterator<?> userIter = userPrincipals.iterator();
61-
Object allowedPrincipal = allowedIter.next();
62-
while (userIter.hasNext()) {
63-
if (allowedPrincipal.equals(userIter.next()))
64-
return true;
68+
// Wildcard Principal is a special principal that allows all
69+
// Check for that first using an identify check. This is
70+
// a fast check that allows us to avoid iterating if true
71+
if (containsWildcardPrincipal(allowedPrincipals)) {
72+
return true;
73+
}
74+
75+
// Iterate over all the principals to see if there are matches
76+
for (Object allowedPrincipal : allowedPrincipals) {
77+
if (contains(allowedPrincipal)) {
78+
return true;
6579
}
6680
}
6781
return false;
6882
}
6983

7084
public abstract Set<Principal> getPrincipals();
7185

86+
public boolean contains(Object principal) {
87+
Set<Principal> principals = getPrincipals();
88+
return principals != null && principals.contains(principal);
89+
}
90+
7291
public String getUserName() {
7392
return userName;
7493
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.activemq.security;
18+
19+
import static org.apache.activemq.security.SecurityContextTest.TestPrincipal.testPrincipal;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertSame;
22+
import static org.junit.Assert.assertTrue;
23+
24+
import java.security.Principal;
25+
import java.util.Objects;
26+
import java.util.Set;
27+
import org.junit.Test;
28+
29+
public class SecurityContextTest {
30+
31+
@Test
32+
public void testIsOneOf() {
33+
SecurityContext context = newContext(Set.of(testPrincipal("one"),
34+
testPrincipal("two"), testPrincipal("three")));
35+
36+
// test valid combos
37+
assertTrue(context.isInOneOf(Set.of(testPrincipal("one"))));
38+
assertTrue(context.isInOneOf(Set.of(testPrincipal("two"))));
39+
assertTrue(context.isInOneOf(Set.of(testPrincipal("three"))));
40+
assertTrue(context.isInOneOf(Set.of(testPrincipal("three"),
41+
testPrincipal("four"), testPrincipal("five"))));
42+
43+
// test no matching
44+
assertFalse(context.isInOneOf(Set.of(testPrincipal("four"),
45+
testPrincipal("five"))));
46+
assertFalse(context.isInOneOf(Set.of()));
47+
// different impl types, should not find
48+
assertFalse(context.isInOneOf(Set.of((Principal) () -> "one")));
49+
50+
// empty set
51+
context = newContext(Set.of());
52+
assertFalse(context.isInOneOf(Set.of(testPrincipal("one"))));
53+
assertFalse(context.isInOneOf(Set.of()));
54+
}
55+
56+
@Test
57+
public void testWildcard() throws Exception {
58+
SecurityContext context = newContext(Set.of(testPrincipal("one"),
59+
testPrincipal("two"), testPrincipal("three")));
60+
61+
// test valid combos
62+
Object wildcard1 = DefaultAuthorizationMap.createGroupPrincipal("*", "someIgnoredClass1");
63+
Object wildcard2 = DefaultAuthorizationMap.createGroupPrincipal("*", "someIgnoredClass2");
64+
65+
// should be identical objects
66+
assertSame(wildcard1, wildcard2);
67+
// wildcard matches anything
68+
assertTrue(context.isInOneOf(Set.of(wildcard1)));
69+
}
70+
71+
private SecurityContext newContext(Set<Principal> principals) {
72+
return new SecurityContext("user") {
73+
@Override
74+
public Set<Principal> getPrincipals() {
75+
return principals;
76+
}
77+
};
78+
}
79+
80+
static class TestPrincipal implements Principal {
81+
private final String name;
82+
83+
private TestPrincipal(String name) {
84+
this.name = name;
85+
}
86+
87+
@Override
88+
public String getName() {
89+
return name;
90+
}
91+
92+
@Override
93+
public boolean equals(Object o) {
94+
if (o == null || getClass() != o.getClass()) {
95+
return false;
96+
}
97+
TestPrincipal that = (TestPrincipal) o;
98+
return Objects.equals(name, that.name);
99+
}
100+
101+
@Override
102+
public int hashCode() {
103+
return Objects.hashCode(name);
104+
}
105+
106+
static TestPrincipal testPrincipal(String name) {
107+
return new TestPrincipal(name);
108+
}
109+
}
110+
111+
}

activemq-unit-tests/src/test/java/org/apache/activemq/security/SimpleSecurityBrokerSystemTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public class SimpleSecurityBrokerSystemTest extends SecurityTestSupport {
6161
public static Principal WILDCARD;
6262
static {
6363
try {
64-
WILDCARD = (Principal) DefaultAuthorizationMap.createGroupPrincipal("*", GroupPrincipal.class.getName());
64+
WILDCARD = (Principal) DefaultAuthorizationMap.createGroupPrincipal(
65+
SecurityContext.WILDCARD, GroupPrincipal.class.getName());
6566
} catch (Exception e) {
6667
LOG.error("Failed to make wildcard principal", e);
6768
}

0 commit comments

Comments
 (0)