This is an automated email from the ASF dual-hosted git repository. vavrtom pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git
The following commit(s) were added to refs/heads/main by this push: new 0b91ded QPID-8488: [Broker-J] Enhance ACL rule with multi-value properties 0b91ded is described below commit 0b91ded6e2c4587ff4ce2324636e2e5f22645295 Author: Marek Laca <mkl...@users.noreply.github.com> AuthorDate: Wed Feb 23 07:58:41 2022 +0100 QPID-8488: [Broker-J] Enhance ACL rule with multi-value properties This closes #116 --- .../security/access/config/AclFileParser.java | 46 +- .../security/access/config/AclRulePredicates.java | 34 +- .../access/config/AclRulePredicatesBuilder.java | 112 ++- .../qpid/server/security/access/config/Rule.java | 11 +- .../access/config/predicates/MultiValue.java | 54 ++ .../config/predicates/RulePredicateBuilder.java | 9 +- ...stractCommonRuleBasedAccessControlProvider.java | 26 +- .../server/security/access/plugins/AclRule.java | 6 +- .../security/access/util/AbstractTreeBranch.java | 98 +++ .../qpid/server/security/access/util/Any.java | 59 ++ .../qpid/server/security/access/util/Empty.java | 57 ++ .../server/security/access/util/FinalBranch.java | 70 ++ .../server/security/access/util/PrefixTree.java | 111 +++ .../server/security/access/util/PrefixTreeSet.java | 33 + .../server/security/access/util/TreeBranch.java | 236 ++++++ .../qpid/server/security/access/util/TreeRoot.java | 76 ++ .../security/access/util/WildCardBranch.java | 102 +++ .../server/security/access/util/WildCardSet.java | 48 ++ .../access/config/AclRulePredicatesTest.java | 43 +- .../predicates/RulePredicateBuilderTest.java | 67 ++ .../config/predicates/RulePredicateTest.java | 57 ++ .../security/access/util/PrefixTreeTest.java | 942 +++++++++++++++++++++ .../management/accesscontrolprovider/RuleBased.js | 18 +- ...Java-Broker-Security-AccessControlProviders.xml | 17 +- 24 files changed, 2251 insertions(+), 81 deletions(-) diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclFileParser.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclFileParser.java index a964af7..bb1c54c 100644 --- a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclFileParser.java +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclFileParser.java @@ -32,26 +32,30 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Queue; +import java.util.Set; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.EventLoggerProvider; import org.apache.qpid.server.security.Result; import org.apache.qpid.server.security.access.config.Rule.Builder; import org.apache.qpid.server.security.access.plugins.RuleOutcome; +import com.google.common.collect.Iterables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public final class AclFileParser { private static final Logger LOGGER = LoggerFactory.getLogger(AclFileParser.class); @@ -81,6 +85,8 @@ public final class AclFileParser static final String PROPERTY_NO_VALUE_MSG = "Incomplete property (no value) at line %d"; static final String GROUP_NOT_SUPPORTED = "GROUP keyword not supported at line %d." + " Groups should defined via a Group Provider, not in the ACL file."; + static final String PROPERTY_NO_CLOSE_BRACKET_MSG = "Incomplete property (no close bracket) at line %d"; + private static final String INVALID_ENUM = "Not a valid %s: %s"; private static final String INVALID_URL = "Cannot convert %s to a readable resource"; @@ -322,7 +328,8 @@ public final class AclFileParser while (i.hasNext()) { final String key = i.next().toLowerCase(Locale.ENGLISH); - final Boolean value = Boolean.valueOf(readValue(i, line)); + final Set<String> values = readValue(i, line); + final Boolean value = Boolean.valueOf(Iterables.getOnlyElement(values)); if (Boolean.TRUE.equals(value)) { @@ -343,7 +350,7 @@ public final class AclFileParser } } - private static String readValue(Iterator<String> tokenIterator, int line) + private static Set<String> readValue(Iterator<String> tokenIterator, int line) { if (!tokenIterator.hasNext()) { @@ -357,7 +364,27 @@ public final class AclFileParser { throw new IllegalConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, line)); } - return tokenIterator.next(); + final String value = tokenIterator.next(); + if ("[".equals(value)) + { + final Set<String> values = new HashSet<>(); + String next; + while (tokenIterator.hasNext()) + { + next = tokenIterator.next(); + if (AclRulePredicates.SEPARATOR.equals(next)) + { + continue; + } + if ("]".equals(next)) + { + return values; + } + values.add(next); + } + throw new IllegalConfigurationException(String.format(PROPERTY_NO_CLOSE_BRACKET_MSG, line)); + } + return Collections.singleton(value); } private RuleOutcome parsePermission(final String text, final int line) @@ -381,11 +408,8 @@ public final class AclFileParser final String typeDescription) { return Optional.ofNullable(map.get(text.toUpperCase(Locale.ENGLISH))) - .orElseThrow(() -> new IllegalConfigurationException(String.format(PARSE_TOKEN_FAILED_MSG, line), - new IllegalArgumentException(String.format( - INVALID_ENUM, - typeDescription, - text)))); + .orElseThrow(() -> new IllegalConfigurationException(String.format(PARSE_TOKEN_FAILED_MSG, line), + new IllegalArgumentException(String.format(INVALID_ENUM, typeDescription, text)))); } private Reader getReaderFromURLString(String urlString) diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicates.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicates.java index de222fd..d1ad844 100644 --- a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicates.java +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicates.java @@ -22,6 +22,7 @@ import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; +import java.util.EnumSet; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -37,7 +38,10 @@ import com.google.common.collect.Iterables; public final class AclRulePredicates extends AbstractMap<Property, Set<Object>> implements RulePredicate { - static final String SEPARATOR = ","; + public static final String SEPARATOR = ","; + + private static final Set<Property> JOIN_PROPERTIES = + EnumSet.of(Property.ATTRIBUTES, Property.FROM_HOSTNAME, Property.FROM_NETWORK); private final Map<Property, Set<Object>> _properties; @@ -64,9 +68,9 @@ public final class AclRulePredicates extends AbstractMap<Property, Set<Object>> _rulePredicate = Objects.requireNonNull(builder.newRulePredicate(factory)); } - public Map<Property, String> getParsedProperties() + public Map<Property, Object> getParsedProperties() { - final Map<Property, String> parsed = new EnumMap<>(Property.class); + final Map<Property, Object> parsed = new EnumMap<>(Property.class); for (final Map.Entry<Property, Set<Object>> entry : _properties.entrySet()) { final Set<Object> values = entry.getValue(); @@ -76,16 +80,30 @@ public final class AclRulePredicates extends AbstractMap<Property, Set<Object>> } else { - parsed.put(entry.getKey(), - values.stream() - .map(Object::toString) - .sorted() - .collect(Collectors.joining(SEPARATOR))); + parsed.put(entry.getKey(), collect(entry.getKey(), values)); } } return parsed; } + private Object collect(Property property, Set<Object> values) + { + if (JOIN_PROPERTIES.contains(property)) + { + return values.stream() + .map(Object::toString) + .sorted() + .collect(Collectors.joining(SEPARATOR)); + } + else + { + return values.stream() + .map(Object::toString) + .sorted() + .collect(Collectors.toList()); + } + } + @Override public boolean matches(LegacyOperation operation, ObjectProperties objectProperties, Subject subject) { diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicatesBuilder.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicatesBuilder.java index bd9d57a..7a6054d 100644 --- a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicatesBuilder.java +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AclRulePredicatesBuilder.java @@ -19,6 +19,7 @@ package org.apache.qpid.server.security.access.config; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashSet; @@ -29,6 +30,8 @@ import java.util.Set; import org.apache.qpid.server.security.access.config.predicates.RulePredicateBuilder; import org.apache.qpid.server.security.access.firewall.FirewallRuleFactory; +import org.apache.qpid.server.security.access.util.PrefixTreeSet; +import org.apache.qpid.server.security.access.util.WildCardSet; import com.google.common.collect.ImmutableSet; import org.slf4j.Logger; @@ -63,7 +66,7 @@ public final class AclRulePredicatesBuilder { for (final Map.Entry<Property, ?> entry : values.entrySet()) { - addPropertyValue(entry.getKey(), Objects.toString(entry.getValue(), null)); + addPropertyValues(entry.getKey(), entry.getValue()); } } } @@ -71,16 +74,32 @@ public final class AclRulePredicatesBuilder public AclRulePredicatesBuilder() { super(); - for (final Property property : Property.values()) - { - _parsedProperties.put(property, Collections.emptySet()); - } _hostNames = new HashSet<>(); - _parsedProperties.put(Property.FROM_HOSTNAME, _hostNames); _networks = new HashSet<>(); - _parsedProperties.put(Property.FROM_NETWORK, _networks); _attributeNames = new HashSet<>(); - _parsedProperties.put(Property.ATTRIBUTES, _attributeNames); + for (final Property property : Property.values()) + { + if (property == Property.FROM_HOSTNAME) + { + _parsedProperties.put(property, _hostNames); + } + else if (property == Property.FROM_NETWORK) + { + _parsedProperties.put(property, _networks); + } + else if (property == Property.ATTRIBUTES) + { + _parsedProperties.put(property, _attributeNames); + } + else if (Property.isBooleanType(property)) + { + _parsedProperties.put(property, new HashSet<>()); + } + else + { + _parsedProperties.put(property, new PrefixTreeSet()); + } + } } public AclRulePredicates build() @@ -103,22 +122,50 @@ public final class AclRulePredicatesBuilder return this; } + public AclRulePredicatesBuilder parse(String key, Set<String> values) + { + final Property property = Property.parse(key); + for (final String value : values) + { + if (addPropertyValue(property, value)) + { + LOGGER.debug("Parsed {} with value {}", property, value); + } + } + return this; + } + public AclRulePredicatesBuilder put(Property property, String value) { addPropertyValue(property, value); return this; } - private boolean addPropertyValue(final Property property, final String value) + private void addPropertyValues(Property property, Object value) + { + if (value instanceof Collection) + { + for (final Object v : (Collection<?>) value) + { + addPropertyValues(property, v); + } + } + else + { + addPropertyValue(property, Objects.toString(value, null)); + } + } + + private boolean addPropertyValue(Property property, String value) { if (property == Property.FROM_HOSTNAME) { - checkFirewallRuleNotAlreadyDefined(property, value, Property.FROM_NETWORK); + checkFirewallRule(property, value, Property.FROM_NETWORK); _hostNames.addAll(splitToSet(value)); } else if (property == Property.FROM_NETWORK) { - checkFirewallRuleNotAlreadyDefined(property, value, Property.FROM_HOSTNAME); + checkFirewallRule(property, value, Property.FROM_HOSTNAME); _networks.addAll(splitToSet(value)); } else if (property == Property.ATTRIBUTES) @@ -137,27 +184,40 @@ public final class AclRulePredicatesBuilder } else { - _parsedProperties.put(property, Collections.singleton(sanitiseValue(property, value))); + addPropertyValueImpl(property, value); } return true; } - private Object sanitiseValue(Property property, String value) + private void addPropertyValueImpl(Property property, String value) { if (value == null) { - return WILD_CARD; + _parsedProperties.put(property, WildCardSet.newSet()); + return; } value = value.trim(); if (value.isEmpty() || WILD_CARD.equals(value)) { - return WILD_CARD; + _parsedProperties.put(property, WildCardSet.newSet()); + return; } + final Set<?> values = _parsedProperties.get(property); if (Property.isBooleanType(property)) { - return parseBoolean(value); + ((Set<Object>) values).add(parseBoolean(value)); + } + else + { + if (values instanceof PrefixTreeSet) + { + ((PrefixTreeSet) values).add(value); + } + else + { + ((Set<Object>) values).add(value); + } } - return value; } private Boolean parseBoolean(String value) @@ -178,14 +238,17 @@ public final class AclRulePredicatesBuilder return Boolean.parseBoolean(value); } - private HashSet<String> splitToSet(String value) + private Set<String> splitToSet(String value) { + if (value == null) + { + return Collections.emptySet(); + } return new HashSet<>(Arrays.asList(value.split(AclRulePredicates.SEPARATOR))); } - private void checkFirewallRuleNotAlreadyDefined(Property property, String value, Property exclusiveProperty) + private void checkFirewallRule(Property property, String value, Property exclusiveProperty) { - checkPropertyAlreadyDefined(property); if (!_parsedProperties.get(exclusiveProperty).isEmpty()) { throw new IllegalStateException( @@ -195,15 +258,6 @@ public final class AclRulePredicatesBuilder } } - private void checkPropertyAlreadyDefined(Property property) - { - if (!_parsedProperties.get(property).isEmpty()) - { - throw new IllegalStateException(String.format("Property '%s' has already been defined", - property.toString().toLowerCase(Locale.ENGLISH))); - } - } - Map<Property, Set<Object>> newProperties() { final Map<Property, Set<Object>> properties = new EnumMap<>(Property.class); diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java index 13c9874..d7d3b66 100644 --- a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java @@ -22,6 +22,7 @@ package org.apache.qpid.server.security.access.config; import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.security.auth.Subject; @@ -138,7 +139,7 @@ public class Rule return _object; } - public Map<Property, String> getAttributes() + public Map<Property, Object> getAttributes() { return _predicates.getParsedProperties(); } @@ -213,7 +214,7 @@ public class Rule } @Override - public Map<Property, String> getAttributes() + public Map<Property, Object> getAttributes() { return _rule.getAttributes(); } @@ -276,6 +277,12 @@ public class Rule return this; } + public Builder withPredicate(String key, Set<String> values) + { + _aclRulePredicatesBuilder.parse(key, values); + return this; + } + public Builder withOwner() { _identity = OWNER; diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/predicates/MultiValue.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/predicates/MultiValue.java new file mode 100644 index 0000000..fa766ba --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/predicates/MultiValue.java @@ -0,0 +1,54 @@ +/* + * 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.qpid.server.security.access.config.predicates; + +import java.util.Objects; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.access.config.LegacyOperation; +import org.apache.qpid.server.security.access.config.ObjectProperties; +import org.apache.qpid.server.security.access.config.Property; +import org.apache.qpid.server.security.access.config.RulePredicate; +import org.apache.qpid.server.security.access.util.PrefixTree; + +class MultiValue implements RulePredicate +{ + private final Property _property; + + private final PrefixTree _tree; + + static RulePredicate newInstance(Property property, PrefixTree tree) + { + return tree == null ? RulePredicate.any() : new MultiValue(property, tree); + } + + private MultiValue(Property property, PrefixTree tree) + { + _property = Objects.requireNonNull(property); + _tree = Objects.requireNonNull(tree); + } + + @Override + public boolean matches(LegacyOperation operation, ObjectProperties objectProperties, Subject subject) + { + final Object value = objectProperties.get(_property); + return (value instanceof String) && _tree.match((String) value); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilder.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilder.java index b7c5332..410066d 100644 --- a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilder.java +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilder.java @@ -28,6 +28,7 @@ import org.apache.qpid.server.security.access.config.AclRulePredicatesBuilder; import org.apache.qpid.server.security.access.config.Property; import org.apache.qpid.server.security.access.config.RulePredicate; import org.apache.qpid.server.security.access.firewall.FirewallRuleFactory; +import org.apache.qpid.server.security.access.util.PrefixTreeSet; public final class RulePredicateBuilder { @@ -74,13 +75,17 @@ public final class RulePredicateBuilder } } - private Set<String> toSet(Collection<?> hostnames) + private Set<String> toSet(Collection<?> values) { - return hostnames.stream().map(Object::toString).collect(Collectors.toSet()); + return values.stream().map(Object::toString).collect(Collectors.toSet()); } private RulePredicate buildGenericPredicate(Property property, Collection<?> values) { + if (values instanceof PrefixTreeSet && values.size() > 2) + { + return MultiValue.newInstance(property, ((PrefixTreeSet) values).toPrefixTree()); + } RulePredicate predicate = RulePredicate.none(); for (final Object value : values) { diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractCommonRuleBasedAccessControlProvider.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractCommonRuleBasedAccessControlProvider.java index f5a888b..a831907 100644 --- a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractCommonRuleBasedAccessControlProvider.java +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractCommonRuleBasedAccessControlProvider.java @@ -29,10 +29,12 @@ import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.qpid.server.logging.EventLoggerProvider; import org.apache.qpid.server.model.CommonAccessControlProvider; @@ -139,13 +141,25 @@ abstract class AbstractCommonRuleBasedAccessControlProvider<X extends AbstractCo .append(rule.getOperation().name()) .append(' ') .append(rule.getObjectType().name()); - for (final Map.Entry<Property, String> entry : rule.getAttributes().entrySet()) + for (final Map.Entry<Property, Object> entry : rule.getAttributes().entrySet()) { - sb.append(' ') - .append(entry.getKey().getCanonicalName()) - .append("=\"") - .append(entry.getValue()) - .append("\""); + if (entry.getValue() == null) + { + continue; + } + sb.append(' ').append(entry.getKey().getCanonicalName()); + + if (entry.getValue() instanceof Collection) + { + final Collection<?> values = (Collection<?>) entry.getValue(); + sb.append("=[\"") + .append(values.stream().map(Object::toString).collect(Collectors.joining("\",\""))) + .append("\"]"); + } + else + { + sb.append("=\"").append(entry.getValue()).append("\""); + } } } diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AclRule.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AclRule.java index bb1bf1e..7451c01 100644 --- a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AclRule.java +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AclRule.java @@ -32,8 +32,12 @@ import org.apache.qpid.server.security.access.config.Property; public interface AclRule extends ManagedAttributeValue { String getIdentity(); + ObjectType getObjectType(); + LegacyOperation getOperation(); - Map<Property,String> getAttributes(); + + Map<Property, Object> getAttributes(); + RuleOutcome getOutcome(); } diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/AbstractTreeBranch.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/AbstractTreeBranch.java new file mode 100644 index 0000000..7b489d1 --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/AbstractTreeBranch.java @@ -0,0 +1,98 @@ +/* + * 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.qpid.server.security.access.util; + +import java.util.Objects; + +abstract class AbstractTreeBranch implements PrefixTree +{ + private static final String NO_PREFIX = "Tree root does not have a prefix"; + private static final String EMPTY_INPUT_PREFIX = "The input prefix can not be empty"; + private static final String EMPTY_INPUT_STRING = "The input string can not be empty"; + + final String _prefix; + + final int _length; + + abstract AbstractTreeBranch mergeString(String str); + + abstract AbstractTreeBranch mergeWildCard(String prefix); + + abstract boolean contains(String str); + + AbstractTreeBranch(String prefix) + { + super(); + _prefix = Objects.requireNonNull(prefix, "The prefix can not be null!"); + _length = _prefix.length(); + } + + AbstractTreeBranch() + { + super(); + _prefix = ""; + _length = 0; + } + + @Override + public String prefix() + { + return _prefix; + } + + @Override + public char firstPrefixCharacter() + { + if (_prefix.isEmpty()) + { + throw new UnsupportedOperationException(NO_PREFIX); + } + return _prefix.charAt(0); + } + + @Override + public PrefixTree mergeWithFinalValue(String str) + { + if (str == null || str.isEmpty()) + { + throw new IllegalArgumentException(EMPTY_INPUT_STRING); + } + return mergeString(str); + } + + @Override + public PrefixTree mergeWithPrefix(String prefix) + { + if (prefix == null || prefix.isEmpty()) + { + throw new IllegalArgumentException(EMPTY_INPUT_PREFIX); + } + return mergeWildCard(prefix); + } + + @Override + public boolean match(String str) + { + if (str == null || str.length() == 0) + { + return false; + } + return contains(str); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/Any.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/Any.java new file mode 100644 index 0000000..9b60ca2 --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/Any.java @@ -0,0 +1,59 @@ +package org.apache.qpid.server.security.access.util; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import com.google.common.collect.Iterators; + +final class Any extends AbstractTreeBranch +{ + static Any INSTANCE = new Any(); + + private Any() + { + super(); + } + + @Override + AbstractTreeBranch mergeString(String str) + { + return this; + } + + @Override + AbstractTreeBranch mergeWildCard(String prefix) + { + return this; + } + + @Override + public boolean match(String str) + { + return str != null; + } + + @Override + boolean contains(String str) + { + return true; + } + + @Override + public Map<Character, PrefixTree> branches() + { + return Collections.emptyMap(); + } + + @Override + public int size() + { + return 1; + } + + @Override + public Iterator<String> iterator() + { + return Iterators.singletonIterator("*"); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/Empty.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/Empty.java new file mode 100644 index 0000000..cc0fc2c --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/Empty.java @@ -0,0 +1,57 @@ +package org.apache.qpid.server.security.access.util; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +final class Empty extends AbstractTreeBranch +{ + static Empty INSTANCE = new Empty(); + + private Empty() + { + super(); + } + + @Override + public Map<Character, PrefixTree> branches() + { + return Collections.emptyMap(); + } + + @Override + public int size() + { + return 0; + } + + @Override + public boolean match(String str) + { + return false; + } + + @Override + AbstractTreeBranch mergeString(String str) + { + return new FinalBranch(str); + } + + @Override + AbstractTreeBranch mergeWildCard(String prefix) + { + return new WildCardBranch(prefix); + } + + @Override + boolean contains(String str) + { + return false; + } + + @Override + public Iterator<String> iterator() + { + return Collections.emptyIterator(); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/FinalBranch.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/FinalBranch.java new file mode 100644 index 0000000..913d107 --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/FinalBranch.java @@ -0,0 +1,70 @@ +/* + * 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.qpid.server.security.access.util; + +import java.util.Iterator; +import java.util.Map; + +final class FinalBranch extends TreeBranch +{ + FinalBranch(String prefix) + { + super(prefix); + } + + FinalBranch(String prefix, Map<Character, AbstractTreeBranch> branches) + { + super(prefix, branches); + } + + FinalBranch(String prefix, AbstractTreeBranch first) + { + super(prefix, first); + } + + @Override + public int size() + { + return 1 + super.size(); + } + + @Override + boolean contains(String str) + { + return _prefix.equals(str) || super.contains(str); + } + + @Override + public Iterator<String> iterator() + { + return new IteratorImpl(this, ""); + } + + @Override + FinalBranch newFinalBranch() + { + return this; + } + + @Override + TreeBranch newTree(String substring, Map<Character, AbstractTreeBranch> branches) + { + return new FinalBranch(substring, branches); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/PrefixTree.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/PrefixTree.java new file mode 100644 index 0000000..331467f --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/PrefixTree.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.qpid.server.security.access.util; + +import java.util.Collection; +import java.util.Map; + +public interface PrefixTree extends Iterable<String> +{ + String prefix(); + + char firstPrefixCharacter(); + + Map<Character, PrefixTree> branches(); + + int size(); + + boolean match(String str); + + default PrefixTree mergeWith(String str) + { + if (str == null || str.isEmpty()) + { + throw new IllegalArgumentException("Prefix tree can not be merged with an empty value"); + } + if ("*".equals(str)) + { + return Any.INSTANCE; + } + if (str.endsWith("*")) + { + return mergeWithPrefix(str.substring(0, str.length() - 1)); + } + return mergeWithFinalValue(str); + } + + default PrefixTree mergeWith(Collection<String> collection) + { + PrefixTree tree = this; + for (final String str : collection) + { + tree = tree.mergeWith(str); + } + return tree; + } + + PrefixTree mergeWithFinalValue(String str); + + PrefixTree mergeWithPrefix(String prefix); + + static PrefixTree empty() + { + return Empty.INSTANCE; + } + + static PrefixTree from(String str) + { + if (str == null || str.isEmpty()) + { + throw new IllegalArgumentException("A non null string is required"); + } + if ("*".equals(str)) + { + return Any.INSTANCE; + } + if (str.endsWith("*")) + { + return fromPrefixWithWildCard(str.substring(0, str.length() - 1)); + } + return fromFinalValue(str); + } + + static PrefixTree from(Collection<String> collection) + { + return empty().mergeWith(collection); + } + + static PrefixTree fromFinalValue(String value) + { + if (value == null || value.isEmpty()) + { + throw new IllegalArgumentException("A non empty value is required"); + } + return new FinalBranch(value); + } + + static PrefixTree fromPrefixWithWildCard(String prefix) + { + if (prefix == null || prefix.isEmpty()) + { + throw new IllegalArgumentException("A non empty prefix is required"); + } + return new WildCardBranch(prefix); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/PrefixTreeSet.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/PrefixTreeSet.java new file mode 100644 index 0000000..1a5fafa --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/PrefixTreeSet.java @@ -0,0 +1,33 @@ +package org.apache.qpid.server.security.access.util; + +import java.util.AbstractSet; +import java.util.Iterator; + +public class PrefixTreeSet extends AbstractSet<String> +{ + private PrefixTree _tree = PrefixTree.empty(); + + @Override + public boolean add(String s) + { + _tree = _tree.mergeWith(s); + return true; + } + + @Override + public Iterator<String> iterator() + { + return _tree.iterator(); + } + + @Override + public int size() + { + return _tree.size(); + } + + public PrefixTree toPrefixTree() + { + return _tree; + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/TreeBranch.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/TreeBranch.java new file mode 100644 index 0000000..529daa2 --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/TreeBranch.java @@ -0,0 +1,236 @@ +/* + * 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.qpid.server.security.access.util; + +import com.google.common.base.Strings; +import com.google.common.collect.Iterators; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +class TreeBranch extends AbstractTreeBranch +{ + final Map<Character, AbstractTreeBranch> _branches; + + TreeBranch(String prefix) + { + super(prefix); + _branches = Collections.emptyMap(); + } + + TreeBranch(Map<Character, AbstractTreeBranch> branches) + { + super(); + _branches = new HashMap<>(branches); + } + + TreeBranch(String prefix, Map<Character, AbstractTreeBranch> branches) + { + super(prefix); + _branches = new HashMap<>(branches); + } + + TreeBranch(String prefix, AbstractTreeBranch first) + { + super(prefix); + _branches = Collections.singletonMap(first.firstPrefixCharacter(), first); + } + + TreeBranch(String prefix, AbstractTreeBranch first, AbstractTreeBranch second) + { + super(prefix); + _branches = new HashMap<>(2); + _branches.put(first.firstPrefixCharacter(), first); + _branches.put(second.firstPrefixCharacter(), second); + } + + TreeBranch(AbstractTreeBranch first, AbstractTreeBranch second) + { + super(); + _branches = new HashMap<>(2); + _branches.put(first.firstPrefixCharacter(), first); + _branches.put(second.firstPrefixCharacter(), second); + } + + @Override + public Map<Character, PrefixTree> branches() + { + return Collections.unmodifiableMap(_branches); + } + + @Override + public int size() + { + return _branches.values().stream().mapToInt(PrefixTree::size).sum(); + } + + @Override + boolean contains(String str) + { + final int length = str.length(); + if (length > _length && str.startsWith(_prefix)) + { + final String subString = str.substring(_length, length); + final PrefixTree subTree = _branches.get(subString.charAt(0)); + if (subTree != null) + { + return subTree.match(subString); + } + } + return false; + } + + @Override + public Iterator<String> iterator() + { + return new IteratorImpl(this); + } + + @Override + AbstractTreeBranch mergeString(String str) + { + final String common = Strings.commonPrefix(str, _prefix); + if (common.isEmpty()) + { + return new TreeRoot(this, new FinalBranch(str)); + } + final int commonLength = common.length(); + if (commonLength == str.length()) + { + if (commonLength == _length) + { + return newFinalBranch(); + } + return new FinalBranch(str, newTree(_prefix.substring(commonLength), _branches)); + } + if (commonLength == _length) + { + final Map<Character, AbstractTreeBranch> branches = new HashMap<>(_branches); + final String subString = str.substring(commonLength); + final char key = subString.charAt(0); + final AbstractTreeBranch branch = branches.get(key); + if (branch != null) + { + branches.put(key, branch.mergeString(subString)); + } + else + { + branches.put(key, new FinalBranch(subString)); + } + return newTree(common, branches); + } + + return new TreeBranch(common, + newTree(_prefix.substring(commonLength), _branches), + new FinalBranch(str.substring(commonLength))); + } + + @Override + AbstractTreeBranch mergeWildCard(String prefix) + { + final String common = Strings.commonPrefix(prefix, _prefix); + if (common.isEmpty()) + { + return new TreeRoot(this, new WildCardBranch(prefix)); + } + final int commonLength = common.length(); + if (commonLength == prefix.length()) + { + return new WildCardBranch(prefix); + } + if (commonLength == _length) + { + final Map<Character, AbstractTreeBranch> branches = new HashMap<>(_branches); + final String subString = prefix.substring(commonLength); + final char key = subString.charAt(0); + final AbstractTreeBranch branch = branches.get(key); + if (branch != null) + { + branches.put(key, branch.mergeWildCard(subString)); + } + else + { + branches.put(key, new WildCardBranch(subString)); + } + return newTree(_prefix, branches); + } + + return new TreeBranch(common, + newTree(_prefix.substring(commonLength), _branches), + new WildCardBranch(prefix.substring(commonLength))); + } + + FinalBranch newFinalBranch() + { + return new FinalBranch(_prefix, _branches); + } + + TreeBranch newTree(String prefix, Map<Character, AbstractTreeBranch> branches) + { + return new TreeBranch(prefix, branches); + } + + static final class IteratorImpl implements Iterator<String> + { + private final String _prefix; + + private final Iterator<AbstractTreeBranch> _tree; + + private Iterator<String> _branch; + + IteratorImpl(TreeBranch root) + { + _prefix = root.prefix(); + _tree = new TreeMap<>(root._branches).values().iterator(); + _branch = Collections.emptyIterator(); + } + + IteratorImpl(TreeBranch root, String firstValue) + { + _prefix = root.prefix(); + _tree = new TreeMap<>(root._branches).values().iterator(); + _branch = Iterators.singletonIterator(firstValue); + } + + @Override + public boolean hasNext() + { + boolean hasBranch = _branch.hasNext(); + while (!hasBranch && _tree.hasNext()) + { + _branch = _tree.next().iterator(); + hasBranch = _branch.hasNext(); + } + return hasBranch; + } + + @Override + public String next() + { + while (!_branch.hasNext() && _tree.hasNext()) + { + _branch = _tree.next().iterator(); + } + return _prefix + _branch.next(); + } + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/TreeRoot.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/TreeRoot.java new file mode 100644 index 0000000..19235a8 --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/TreeRoot.java @@ -0,0 +1,76 @@ +/* + * 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.qpid.server.security.access.util; + +import java.util.HashMap; +import java.util.Map; + +final class TreeRoot extends TreeBranch +{ + TreeRoot(AbstractTreeBranch first, AbstractTreeBranch second) + { + super(first, second); + } + + TreeRoot(Map<Character, AbstractTreeBranch> map) + { + super(map); + } + + @Override + boolean contains(String str) + { + final PrefixTree subTree = _branches.get(str.charAt(0)); + return subTree != null && subTree.match(str); + } + + @Override + AbstractTreeBranch mergeString(String str) + { + final Map<Character, AbstractTreeBranch> branches = new HashMap<>(_branches); + final char key = str.charAt(0); + final AbstractTreeBranch branch = _branches.get(key); + if (branch == null) + { + branches.put(key, new FinalBranch(str)); + } + else + { + branches.put(key, branch.mergeString(str)); + } + return new TreeRoot(branches); + } + + @Override + AbstractTreeBranch mergeWildCard(String prefix) + { + final Map<Character, AbstractTreeBranch> branches = new HashMap<>(_branches); + final char key = prefix.charAt(0); + final AbstractTreeBranch branch = _branches.get(key); + if (branch == null) + { + branches.put(key, new WildCardBranch(prefix)); + } + else + { + branches.put(key, branch.mergeWildCard(prefix)); + } + return new TreeRoot(branches); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/WildCardBranch.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/WildCardBranch.java new file mode 100644 index 0000000..0854cb6 --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/WildCardBranch.java @@ -0,0 +1,102 @@ +/* + * 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.qpid.server.security.access.util; + +import com.google.common.base.Strings; +import com.google.common.collect.Iterators; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +final class WildCardBranch extends AbstractTreeBranch +{ + WildCardBranch(String prefix) + { + super(prefix); + } + + @Override + public int size() + { + return 1; + } + + @Override + public Map<Character, PrefixTree> branches() + { + return Collections.emptyMap(); + } + + @Override + boolean contains(String str) + { + return str.startsWith(_prefix); + } + + @Override + public Iterator<String> iterator() + { + return Iterators.singletonIterator(_prefix + "*"); + } + + @Override + AbstractTreeBranch mergeString(String str) + { + final String common = Strings.commonPrefix(str, _prefix); + if (common.isEmpty()) + { + return new TreeRoot(this, new FinalBranch(str)); + } + final int commonLength = common.length(); + if (commonLength == _length) + { + return this; + } + if (commonLength == str.length()) + { + return new FinalBranch(common, new WildCardBranch(_prefix.substring(commonLength))); + } + return new TreeBranch(common, + new WildCardBranch(_prefix.substring(commonLength)), + new FinalBranch(str.substring(commonLength))); + } + + @Override + AbstractTreeBranch mergeWildCard(String prefix) + { + final String common = Strings.commonPrefix(prefix, _prefix); + if (common.isEmpty()) + { + return new TreeRoot(this, new WildCardBranch(prefix)); + } + final int commonLength = common.length(); + if (commonLength == _length) + { + return this; + } + if (commonLength == prefix.length()) + { + return new WildCardBranch(prefix); + } + return new TreeBranch(common, + new WildCardBranch(_prefix.substring(commonLength)), + new WildCardBranch(prefix.substring(commonLength))); + } +} diff --git a/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/WildCardSet.java b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/WildCardSet.java new file mode 100644 index 0000000..cf76d0f --- /dev/null +++ b/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/util/WildCardSet.java @@ -0,0 +1,48 @@ +package org.apache.qpid.server.security.access.util; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.qpid.server.security.access.config.AclRulePredicatesBuilder; + +import com.google.common.collect.Iterators; + +public class WildCardSet extends AbstractSet<Object> +{ + private static final WildCardSet INSTANCE = new WildCardSet(); + + public static WildCardSet newSet() + { + return INSTANCE; + } + + private WildCardSet() + { + super(); + } + + @Override + public boolean add(Object o) + { + return false; + } + + @Override + public boolean addAll(Collection<?> c) + { + return false; + } + + @Override + public Iterator<Object> iterator() + { + return Iterators.singletonIterator(AclRulePredicatesBuilder.WILD_CARD); + } + + @Override + public int size() + { + return 1; + } +} diff --git a/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/AclRulePredicatesTest.java b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/AclRulePredicatesTest.java index 48b2a8e..76dbac5 100644 --- a/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/AclRulePredicatesTest.java +++ b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/AclRulePredicatesTest.java @@ -18,8 +18,10 @@ */ package org.apache.qpid.server.security.access.config; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -116,21 +118,6 @@ public class AclRulePredicatesTest extends UnitTestBase } @Test - public void testParseThrowsExceptionIfHostnameSpecified2Times() - { - _builder.parse(FROM_NETWORK.name(), "network"); - try - { - _builder.parse(FROM_NETWORK.name(), "network2"); - fail("Exception not thrown"); - } - catch (IllegalStateException e) - { - // pass - } - } - - @Test public void testParseAttributesRule() { final String attributes = "attribute1,attribute2"; @@ -163,7 +150,7 @@ public class AclRulePredicatesTest extends UnitTestBase .parse(FROM_NETWORK.name(), "network") .build(_firewallRuleFactory); - final Map<Property, String> properties = predicates.getParsedProperties(); + final Map<Property, Object> properties = predicates.getParsedProperties(); assertEquals(3, properties.size()); assertEquals(properties.get(NAME), "name"); @@ -171,7 +158,6 @@ public class AclRulePredicatesTest extends UnitTestBase assertEquals(properties.get(FROM_NETWORK), "network"); } - @Test public void testAttributeNames() { @@ -263,6 +249,29 @@ public class AclRulePredicatesTest extends UnitTestBase } @Test + public void testParse_MultiValue() + { + final String nameA = "name.A"; + AclRulePredicates predicates = _builder.parse(NAME.name(), nameA).build(_firewallRuleFactory); + assertEquals(Collections.singleton(nameA), predicates.get(NAME)); + + final String nameB = "name.B"; + predicates = _builder.parse(NAME.name(), nameB).build(_firewallRuleFactory); + assertEquals(new HashSet<>(Arrays.asList(nameA, nameB)), predicates.get(NAME)); + + final String nameX = "name.*"; + predicates = _builder.parse(NAME.name(), nameX).build(_firewallRuleFactory); + assertEquals(Collections.singleton(nameX), predicates.get(NAME)); + + final String nameC = "name.C"; + predicates = _builder.parse(NAME.name(), nameC).build(_firewallRuleFactory); + assertEquals(Collections.singleton(nameX), predicates.get(NAME)); + + predicates = _builder.parse(NAME.name(), "*").build(_firewallRuleFactory); + assertEquals(Collections.singleton("*"), predicates.get(NAME)); + } + + @Test public void testEqualsHashCode() { _builder.parse(NAME.name(), "name"); diff --git a/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilderTest.java b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilderTest.java index bfbebe4..0418299 100644 --- a/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilderTest.java +++ b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateBuilderTest.java @@ -31,6 +31,8 @@ import org.apache.qpid.server.security.access.config.ObjectProperties; import org.apache.qpid.server.security.access.config.Property; import org.apache.qpid.server.security.access.config.RulePredicate; import org.apache.qpid.server.security.access.firewall.FirewallRuleFactory; +import org.apache.qpid.server.security.access.util.PrefixTreeSet; +import org.apache.qpid.server.security.access.util.WildCardSet; import org.apache.qpid.server.security.auth.TestPrincipalUtils; import org.apache.qpid.test.utils.UnitTestBase; @@ -305,4 +307,69 @@ public class RulePredicateBuilderTest extends UnitTestBase assertFalse(predicate.matches(LegacyOperation.PUBLISH, new ObjectProperties(), _subject)); } + + @Test + public void testMatch_PrefixTree() + { + final PrefixTreeSet tree = new PrefixTreeSet(); + tree.addAll(Arrays.asList("Exchange.public.*", "Exchange.private.A", "Exchange.private.B")); + final RulePredicate predicate = _builder.build(Collections.singletonMap(NAME, tree)); + + ObjectProperties op = new ObjectProperties(NAME, "Exchange.private.A"); + assertTrue(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + + op = new ObjectProperties(NAME, "Exchange.private.B"); + assertTrue(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + + op = new ObjectProperties(NAME, "Exchange.public.ABC"); + assertTrue(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + } + + @Test + public void testDoesNotMatch_PrefixTree() + { + final PrefixTreeSet tree = new PrefixTreeSet(); + tree.addAll(Arrays.asList("Exchange.public.*", "Exchange.private.A", "Exchange.private.B")); + final RulePredicate predicate = _builder.build(Collections.singletonMap(NAME, tree)); + + final ObjectProperties op = new ObjectProperties(NAME, "Exchange.private.xyz"); + assertFalse(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + } + + @Test + public void testMatch_PrefixTree_single() + { + final PrefixTreeSet tree = new PrefixTreeSet(); + tree.add("Exchange.public.*"); + final RulePredicate predicate = _builder.build(Collections.singletonMap(NAME, tree)); + + final ObjectProperties op = new ObjectProperties(NAME, "Exchange.public.A"); + assertTrue(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + } + + @Test + public void testDoesNotMatch_PrefixTree_single() + { + final PrefixTreeSet tree = new PrefixTreeSet(); + tree.add("Exchange.public.*"); + final RulePredicate predicate = _builder.build(Collections.singletonMap(NAME, tree)); + + final ObjectProperties op = new ObjectProperties(NAME, "Exchange.private.xyz"); + assertFalse(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + } + + @Test + public void testMatch_WildcardSet() + { + final RulePredicate predicate = _builder.build(Collections.singletonMap(NAME, WildCardSet.newSet())); + + ObjectProperties op = new ObjectProperties(NAME, "Exchange.private.A"); + assertTrue(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + + op = new ObjectProperties(NAME, "Exchange.private.B"); + assertTrue(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + + op = new ObjectProperties(NAME, "Exchange.public.ABC"); + assertTrue(predicate.matches(LegacyOperation.PUBLISH, op, _subject)); + } } diff --git a/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateTest.java b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateTest.java index a9baaea..1f8478e 100644 --- a/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateTest.java +++ b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/predicates/RulePredicateTest.java @@ -416,4 +416,61 @@ public class RulePredicateTest extends UnitTestBase assertFalse(rule.matches(LegacyOperation.PUBLISH, ObjectType.EXCHANGE, new ObjectProperties(), _subject)); assertFalse(rule.anyPropertiesMatch()); } + + @Test + public void testMatch_Properties_MultiValue() + { + final Rule rule = _builder + .withPredicate(Property.ROUTING_KEY, "broadcast.public") + .withPredicate(Property.NAME, "broadcast.A") + .withPredicate(Property.NAME, "broadcast.B") + .withPredicate(Property.NAME, "broadcast.X.*") + .withOperation(LegacyOperation.PUBLISH) + .withObject(ObjectType.EXCHANGE) + .withOutcome(RuleOutcome.ALLOW) + .build(_firewallRuleFactory); + + ObjectProperties action = new ObjectProperties(); + action.put(Property.ROUTING_KEY, "broadcast.public"); + action.put(Property.NAME, "broadcast.A"); + action.put(Property.METHOD_NAME, "publish"); + + assertTrue(rule.matches(LegacyOperation.PUBLISH, ObjectType.EXCHANGE, action, _subject)); + + action = new ObjectProperties(); + action.put(Property.ROUTING_KEY, "broadcast.public"); + action.put(Property.NAME, "broadcast.B"); + action.put(Property.METHOD_NAME, "publish"); + + assertTrue(rule.matches(LegacyOperation.PUBLISH, ObjectType.EXCHANGE, action, _subject)); + + action = new ObjectProperties(); + action.put(Property.ROUTING_KEY, "broadcast.public"); + action.put(Property.NAME, "broadcast.X.new"); + action.put(Property.METHOD_NAME, "publish"); + + assertTrue(rule.matches(LegacyOperation.PUBLISH, ObjectType.EXCHANGE, action, _subject)); + } + + @Test + public void testDoesNotMatch_Properties_MultiValue() + { + final Rule rule = _builder + .withPredicate(Property.ROUTING_KEY, "generic.public") + .withPredicate(Property.NAME, "broadcast.A") + .withPredicate(Property.NAME, "broadcast.B") + .withPredicate(Property.NAME, "broadcast.X.*") + .withOperation(LegacyOperation.PUBLISH) + .withObject(ObjectType.EXCHANGE) + .withOutcome(RuleOutcome.ALLOW) + .build(_firewallRuleFactory); + + final ObjectProperties action = new ObjectProperties(); + action.put(Property.ROUTING_KEY, "broadcast.public"); + action.setName("broadcast"); + action.put(Property.METHOD_NAME, "publish"); + + assertFalse(rule.matches(LegacyOperation.PUBLISH, ObjectType.EXCHANGE, action, _subject)); + assertFalse(rule.matches(LegacyOperation.PUBLISH, ObjectType.EXCHANGE, new ObjectProperties(), _subject)); + } } \ No newline at end of file diff --git a/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/util/PrefixTreeTest.java b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/util/PrefixTreeTest.java new file mode 100644 index 0000000..41473c4 --- /dev/null +++ b/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/util/PrefixTreeTest.java @@ -0,0 +1,942 @@ +/* + * 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.qpid.server.security.access.util; + +import org.apache.qpid.test.utils.UnitTestBase; + +import com.google.common.collect.Streams; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class PrefixTreeTest extends UnitTestBase +{ + @Test + public void testPrefixWithWildcard_Single() + { + testPrefixWithWildcard_Single(PrefixTree.fromPrefixWithWildCard("abcd")); + testPrefixWithWildcard_Single(PrefixTree.fromPrefixWithWildCard("abcd").mergeWithPrefix("abcd")); + testPrefixWithWildcard_Single(PrefixTree.fromPrefixWithWildCard("abcdXYZ").mergeWithPrefix("abcd")); + testPrefixWithWildcard_Single(PrefixTree.fromPrefixWithWildCard("abcd").mergeWithPrefix("abcdXYZ")); + + testPrefixWithWildcard_Single(PrefixTree.fromPrefixWithWildCard("abcd").mergeWithFinalValue("abcd")); + testPrefixWithWildcard_Single(PrefixTree.fromFinalValue("abcd").mergeWithPrefix("abcd")); + testPrefixWithWildcard_Single(PrefixTree.fromPrefixWithWildCard("abcd").mergeWithFinalValue("abcdXYZ")); + testPrefixWithWildcard_Single(PrefixTree.fromFinalValue("abcdXYZ").mergeWithPrefix("abcd")); + } + + private void testPrefixWithWildcard_Single(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(1, tree.size()); + for (final String str : tree) + { + assertEquals("abcd*", str); + } + assertEquals("abcd", tree.prefix()); + assertEquals('a', tree.firstPrefixCharacter()); + assertNotNull(tree.branches()); + assertTrue(tree.branches().isEmpty()); + + assertTrue(tree.match("abcd")); + assertTrue(tree.match("abcd.e")); + assertFalse(tree.match("Abcdx")); + assertFalse(tree.match("abc")); + assertFalse(tree.match("ab")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testPrefixWithWildcard() + { + final String[] strings = new String[]{"exchange.public.*", "exchange.private.*", "response.public.*", "response.private.*", "response.*"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(strs[0]) + .mergeWith(strs[1]) + .mergeWith(strs[2]) + .mergeWith(strs[3]) + .mergeWith(strs[4]); + testPrefixWithWildcard(tree); + testPrefixWithWildcard(PrefixTree.from(Arrays.asList(strs))); + } + } + + private void testPrefixWithWildcard(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(3, tree.size()); + final String[] array = new String[3]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"exchange.private.*", "exchange.public.*", "response.*"}, array); + assertNotNull(tree.branches()); + assertEquals(2, tree.branches().size()); + + PrefixTree branch = tree.branches().get('e'); + assertNotNull(branch); + assertEquals('e', branch.firstPrefixCharacter()); + assertEquals("exchange.p", branch.prefix()); + assertEquals(2, branch.size()); + assertEquals(2, branch.branches().size()); + assertNotNull(branch.branches().get('r')); + assertEquals("rivate.", branch.branches().get('r').prefix()); + assertNotNull(branch.branches().get('u')); + assertEquals("ublic.", branch.branches().get('u').prefix()); + + branch = tree.branches().get('r'); + assertNotNull(branch); + assertEquals('r', branch.firstPrefixCharacter()); + assertEquals("response.", branch.prefix()); + assertEquals(1, branch.size()); + assertEquals(0, branch.branches().size()); + + assertTrue(tree.match("response.x")); + assertTrue(tree.match("response.")); + assertTrue(tree.match("exchange.private.A")); + assertTrue(tree.match("exchange.private.")); + assertTrue(tree.match("exchange.public.B")); + assertTrue(tree.match("exchange.public.")); + assertFalse(tree.match("response")); + assertFalse(tree.match("exchange.private")); + assertFalse(tree.match("exchange.public")); + + assertFalse(tree.match("exchange.rest")); + assertFalse(tree.match("reg")); + assertFalse(tree.match("error")); + assertFalse(tree.match("warning")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testPrefixWithWildcard_RootWith3Branches() + { + final String[] strings = new String[]{"A", "B", "C"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.fromPrefixWithWildCard(strs[0]) + .mergeWithPrefix(strs[1]) + .mergeWithPrefix(strs[2]); + testPrefixWithWildcard_RootWith3Branches(tree); + } + } + + private void testPrefixWithWildcard_RootWith3Branches(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(3, tree.size()); + final String[] array = new String[3]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"A*", "B*", "C*"}, array); + assertNotNull(tree.branches()); + assertEquals(3, tree.branches().size()); + + PrefixTree branch = tree.branches().get('A'); + assertNotNull(branch); + assertEquals('A', branch.firstPrefixCharacter()); + assertEquals("A", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('B'); + assertNotNull(branch); + assertEquals('B', branch.firstPrefixCharacter()); + assertEquals("B", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('C'); + assertNotNull(branch); + assertEquals('C', branch.firstPrefixCharacter()); + assertEquals("C", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + assertTrue(tree.match("A")); + assertTrue(tree.match("Ax")); + assertTrue(tree.match("B")); + assertTrue(tree.match("Bx")); + assertTrue(tree.match("C")); + assertTrue(tree.match("Cx")); + + assertFalse(tree.match("x")); + assertFalse(tree.match("b")); + assertFalse(tree.match("cC")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testPrefixWithWildcard_BranchSplit() + { + final String[] strings = new String[]{"AB*", "AC*"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(strs[0]) + .mergeWith(strs[1]); + testPrefixWithWildcard_BranchSplit(tree); + testPrefixWithWildcard_BranchSplit(PrefixTree.from(Arrays.asList(strs))); + } + } + + private void testPrefixWithWildcard_BranchSplit(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(2, tree.size()); + final String[] array = new String[2]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"AB*", "AC*"}, array); + assertEquals(2, tree.size()); + assertNotNull(tree.branches()); + assertEquals(2, tree.branches().size()); + + assertEquals('A', tree.firstPrefixCharacter()); + assertEquals("A", tree.prefix()); + + PrefixTree branch = tree.branches().get('B'); + assertNotNull(branch); + assertEquals('B', branch.firstPrefixCharacter()); + assertEquals("B", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('C'); + assertNotNull(branch); + assertEquals('C', branch.firstPrefixCharacter()); + assertEquals("C", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + assertTrue(tree.match("AB")); + assertTrue(tree.match("ABx")); + assertTrue(tree.match("AC")); + assertTrue(tree.match("ACx")); + + assertFalse(tree.match("A")); + assertFalse(tree.match("Ab")); + assertFalse(tree.match("Ac")); + assertFalse(tree.match("b")); + assertFalse(tree.match("cC")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testExactString_Single() + { + testExactString_Single(PrefixTree.fromFinalValue("abcd")); + testExactString_Single(PrefixTree.fromFinalValue("abcd").mergeWithFinalValue("abcd")); + } + + private void testExactString_Single(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(1, tree.size()); + for (final String str : tree) + { + assertEquals("abcd", str); + } + assertEquals("abcd", tree.prefix()); + assertEquals('a', tree.firstPrefixCharacter()); + assertNotNull(tree.branches()); + assertTrue(tree.branches().isEmpty()); + + assertTrue(tree.match("abcd")); + assertFalse(tree.match("aBcd")); + assertFalse(tree.match("abcd.")); + assertFalse(tree.match("abc")); + assertFalse(tree.match("x")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testExactString() + { + final String[] strings = new String[]{"exchange.public", "exchange.private", "response.public", "response.private", "response"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(strs[0]) + .mergeWith(strs[1]) + .mergeWith(strs[2]) + .mergeWith(strs[3]) + .mergeWith(strs[4]); + testExactString(tree); + testExactString(PrefixTree.from(Arrays.asList(strs))); + } + } + + private void testExactString(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(5, tree.size()); + final String[] array = new String[5]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"exchange.private", "exchange.public", "response", "response.private", "response.public"}, array); + assertNotNull(tree.branches()); + assertEquals(2, tree.branches().size()); + + PrefixTree branch = tree.branches().get('e'); + assertNotNull(branch); + assertEquals('e', branch.firstPrefixCharacter()); + assertEquals("exchange.p", branch.prefix()); + assertEquals(2, branch.size()); + assertEquals(2, branch.branches().size()); + assertNotNull(branch.branches().get('r')); + assertEquals("rivate", branch.branches().get('r').prefix()); + assertNotNull(branch.branches().get('u')); + assertEquals("ublic", branch.branches().get('u').prefix()); + + branch = tree.branches().get('r'); + assertNotNull(branch); + assertEquals('r', branch.firstPrefixCharacter()); + assertEquals("response", branch.prefix()); + assertEquals(3, branch.size()); + assertEquals(1, branch.branches().size()); + + branch = branch.branches().get('.'); + assertNotNull(branch); + assertEquals('.', branch.firstPrefixCharacter()); + assertEquals(".p", branch.prefix()); + assertEquals(2, branch.size()); + assertEquals(2, branch.branches().size()); + + assertNotNull(branch.branches().get('r')); + assertEquals("rivate", branch.branches().get('r').prefix()); + assertNotNull(branch.branches().get('u')); + assertEquals("ublic", branch.branches().get('u').prefix()); + + assertTrue(tree.match("exchange.private")); + assertTrue(tree.match("exchange.public")); + assertTrue(tree.match("response")); + assertTrue(tree.match("response.private")); + assertTrue(tree.match("response.public")); + + assertFalse(tree.match("exchange.privat")); + assertFalse(tree.match("exchange.privateX")); + assertFalse(tree.match("exchange.publi")); + assertFalse(tree.match("exchange.publicX")); + assertFalse(tree.match("respons")); + assertFalse(tree.match("response.p")); + assertFalse(tree.match("response.privat")); + assertFalse(tree.match("response.privateX")); + assertFalse(tree.match("response.publi")); + assertFalse(tree.match("response.publicX")); + + assertFalse(tree.match("exchange.rest")); + assertFalse(tree.match("reg")); + assertFalse(tree.match("error")); + assertFalse(tree.match("warning")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testExactString_RootWith3Branches() + { + final String[] strings = new String[]{"A", "B", "C"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.fromFinalValue(strs[0]) + .mergeWithFinalValue(strs[1]) + .mergeWithFinalValue(strs[2]); + testExactString_RootWith3Branches(tree); + } + } + + private void testExactString_RootWith3Branches(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(3, tree.size()); + final String[] array = new String[3]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"A", "B", "C"}, array); + assertNotNull(tree.branches()); + assertEquals(3, tree.branches().size()); + + PrefixTree branch = tree.branches().get('A'); + assertNotNull(branch); + assertEquals('A', branch.firstPrefixCharacter()); + assertEquals("A", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('B'); + assertNotNull(branch); + assertEquals('B', branch.firstPrefixCharacter()); + assertEquals("B", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('C'); + assertNotNull(branch); + assertEquals('C', branch.firstPrefixCharacter()); + assertEquals("C", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + assertTrue(tree.match("A")); + assertTrue(tree.match("B")); + assertTrue(tree.match("C")); + + assertFalse(tree.match("Ax")); + assertFalse(tree.match("b")); + assertFalse(tree.match("Cc")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testExactString_BranchSpit() + { + final String[] strings = new String[]{"A", "AB", "AC"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(strs[0]) + .mergeWith(strs[1]) + .mergeWith(strs[2]); + testExactString_BranchSpit(tree); + testExactString_BranchSpit(PrefixTree.from(Arrays.asList(strs))); + } + } + + private void testExactString_BranchSpit(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(3, tree.size()); + final String[] array = new String[3]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"A", "AB", "AC"}, array); + assertNotNull(tree.branches()); + assertEquals(2, tree.branches().size()); + assertEquals("A", tree.prefix()); + assertEquals('A', tree.firstPrefixCharacter()); + + PrefixTree branch = tree.branches().get('B'); + assertNotNull(branch); + assertEquals('B', branch.firstPrefixCharacter()); + assertEquals("B", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('C'); + assertNotNull(branch); + assertEquals('C', branch.firstPrefixCharacter()); + assertEquals("C", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + assertTrue(tree.match("A")); + assertTrue(tree.match("AB")); + assertTrue(tree.match("AC")); + + assertFalse(tree.match("Ax")); + assertFalse(tree.match("Ab")); + assertFalse(tree.match("Ac")); + assertFalse(tree.match("aa")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testMixing() + { + final String[] strings = new String[]{"exchange.public", "exchange.private.A", "exchange.private.*", "response.public", "response.private", "response.p*", "response"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(strs[0]) + .mergeWith(strs[1]) + .mergeWith(strs[2]) + .mergeWith(strs[3]) + .mergeWith(strs[4]) + .mergeWith(strs[5]) + .mergeWith(strs[6]); + testMixing(tree); + testMixing(PrefixTree.from(Arrays.asList(strs))); + } + } + + private void testMixing(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(4, tree.size()); + final String[] array = new String[4]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"exchange.private.*", "exchange.public", "response", "response.p*"}, array); + assertNotNull(tree.branches()); + assertEquals(2, tree.branches().size()); + + PrefixTree branch = tree.branches().get('e'); + assertNotNull(branch); + assertEquals('e', branch.firstPrefixCharacter()); + assertEquals("exchange.p", branch.prefix()); + assertEquals(2, branch.size()); + assertEquals(2, branch.branches().size()); + assertNotNull(branch.branches().get('r')); + assertEquals("rivate.", branch.branches().get('r').prefix()); + assertNotNull(branch.branches().get('u')); + assertEquals("ublic", branch.branches().get('u').prefix()); + + branch = tree.branches().get('r'); + assertNotNull(branch); + assertEquals('r', branch.firstPrefixCharacter()); + assertEquals("response", branch.prefix()); + assertEquals(2, branch.size()); + assertEquals(1, branch.branches().size()); + + branch = branch.branches().get('.'); + assertNotNull(branch); + assertEquals('.', branch.firstPrefixCharacter()); + assertEquals(".p", branch.prefix()); + assertEquals(1, branch.size()); + assertTrue(branch.branches().isEmpty()); + + + assertTrue(tree.match("exchange.private.A")); + assertTrue(tree.match("exchange.private.")); + assertTrue(tree.match("exchange.public")); + assertTrue(tree.match("response")); + assertTrue(tree.match("response.private")); + assertTrue(tree.match("response.public")); + assertTrue(tree.match("response.p")); + + assertFalse(tree.match("exchange.privat")); + assertFalse(tree.match("exchange.privateX")); + assertFalse(tree.match("exchange.publi")); + assertFalse(tree.match("exchange.publicX")); + assertFalse(tree.match("respons")); + assertFalse(tree.match("response.")); + + assertFalse(tree.match("exchange.rest")); + assertFalse(tree.match("reg")); + assertFalse(tree.match("error")); + assertFalse(tree.match("warning")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testMixing_BranchSplit() + { + final String[] strings = new String[]{"AB*", "AC*", "AD", "AE"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(strs[0]) + .mergeWith(strs[1]) + .mergeWith(strs[2]) + .mergeWith(strs[3]); + testMixing_BranchSplit(tree); + testMixing_BranchSplit(PrefixTree.from(Arrays.asList(strs))); + } + } + + private void testMixing_BranchSplit(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(4, tree.size()); + final String[] array = new String[4]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"AB*", "AC*", "AD", "AE"}, array); + assertNotNull(tree.branches()); + assertEquals(4, tree.branches().size()); + + assertEquals('A', tree.firstPrefixCharacter()); + assertEquals("A", tree.prefix()); + + PrefixTree branch = tree.branches().get('B'); + assertNotNull(branch); + assertEquals('B', branch.firstPrefixCharacter()); + assertEquals("B", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('C'); + assertNotNull(branch); + assertEquals('C', branch.firstPrefixCharacter()); + assertEquals("C", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('D'); + assertNotNull(branch); + assertEquals('D', branch.firstPrefixCharacter()); + assertEquals("D", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + branch = tree.branches().get('E'); + assertNotNull(branch); + assertEquals('E', branch.firstPrefixCharacter()); + assertEquals("E", branch.prefix()); + assertTrue(branch.branches().isEmpty()); + + assertTrue(tree.match("AB")); + assertTrue(tree.match("ABx")); + assertTrue(tree.match("AC")); + assertTrue(tree.match("ACx")); + assertTrue(tree.match("AD")); + assertTrue(tree.match("AE")); + + assertFalse(tree.match("A")); + assertFalse(tree.match("Ab")); + assertFalse(tree.match("Ac")); + assertFalse(tree.match("Ad")); + assertFalse(tree.match("aE")); + assertFalse(tree.match("ADx")); + assertFalse(tree.match("b")); + assertFalse(tree.match("cC")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testMixing_BranchSplit2() + { + final String[] strings = new String[]{"AXB*", "AXC*", "AYD", "AYE"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(strs[0]) + .mergeWith(strs[1]) + .mergeWith(strs[2]) + .mergeWith(strs[3]); + testMixing_BranchSplit2(tree); + testMixing_BranchSplit2(PrefixTree.from(Arrays.asList(strs))); + } + } + + private void testMixing_BranchSplit2(PrefixTree tree) + { + assertNotNull(tree); + assertEquals(4, tree.size()); + final String[] array = new String[4]; + int i = 0; + for (final String str : tree) + { + array[i++] = str; + } + assertArrayEquals(new String[]{"AXB*", "AXC*", "AYD", "AYE"}, array); + assertNotNull(tree.branches()); + assertEquals(2, tree.branches().size()); + + assertEquals('A', tree.firstPrefixCharacter()); + assertEquals("A", tree.prefix()); + + PrefixTree branch = tree.branches().get('X'); + assertNotNull(branch); + assertEquals('X', branch.firstPrefixCharacter()); + assertEquals("X", branch.prefix()); + assertEquals(2, branch.branches().size()); + + PrefixTree subBranch = branch.branches().get('B'); + assertNotNull(subBranch); + assertEquals('B', subBranch.firstPrefixCharacter()); + assertEquals("B", subBranch.prefix()); + assertTrue(subBranch.branches().isEmpty()); + + subBranch = branch.branches().get('C'); + assertNotNull(branch); + assertEquals('C', subBranch.firstPrefixCharacter()); + assertEquals("C", subBranch.prefix()); + assertTrue(subBranch.branches().isEmpty()); + + branch = tree.branches().get('Y'); + assertNotNull(branch); + assertEquals('Y', branch.firstPrefixCharacter()); + assertEquals("Y", branch.prefix()); + assertEquals(2, branch.branches().size()); + + subBranch = branch.branches().get('D'); + assertNotNull(subBranch); + assertEquals('D', subBranch.firstPrefixCharacter()); + assertEquals("D", subBranch.prefix()); + assertTrue(subBranch.branches().isEmpty()); + + subBranch = branch.branches().get('E'); + assertNotNull(subBranch); + assertEquals('E', subBranch.firstPrefixCharacter()); + assertEquals("E", subBranch.prefix()); + assertTrue(subBranch.branches().isEmpty()); + + assertTrue(tree.match("AXB")); + assertTrue(tree.match("AXBx")); + assertTrue(tree.match("AXC")); + assertTrue(tree.match("AXCx")); + assertTrue(tree.match("AYD")); + assertTrue(tree.match("AYE")); + + assertFalse(tree.match("A")); + assertFalse(tree.match("AXb")); + assertFalse(tree.match("AXc")); + assertFalse(tree.match("AYd")); + assertFalse(tree.match("aYE")); + assertFalse(tree.match("AYDx")); + assertFalse(tree.match("b")); + assertFalse(tree.match("cC")); + assertFalse(tree.match("")); + assertFalse(tree.match(null)); + } + + @Test + public void testFrom_Exception() + { + try + { + PrefixTree.from((String) null); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + + try + { + PrefixTree.from(""); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testFromFinalValue_Exception() + { + try + { + PrefixTree.fromFinalValue(null); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + + try + { + PrefixTree.from(""); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testFromPrefixWithWildCard_Exception() + { + try + { + PrefixTree.fromPrefixWithWildCard(null); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + + try + { + PrefixTree.fromPrefixWithWildCard(""); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testMergeWith_Exception() + { + final PrefixTree tree = PrefixTree.from("A"); + try + { + tree.mergeWith((String) null); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + + try + { + tree.mergeWith(""); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testMergeWithPrefix_Exception() + { + final PrefixTree tree = PrefixTree.from("A"); + try + { + tree.mergeWithPrefix(null); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + + try + { + tree.mergeWithPrefix(""); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testMergeWithFinalValue_Exception() + { + final PrefixTree tree = PrefixTree.from("A"); + try + { + tree.mergeWithFinalValue(null); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + + try + { + tree.mergeWithFinalValue(""); + fail(); + } + catch (IllegalArgumentException e) + { + assertNotNull(e.getMessage()); + } + } + + @Test + public void testFirstPrefixCharacter() + { + final PrefixTree tree = PrefixTree.fromFinalValue("A").mergeWithFinalValue("B"); + try + { + tree.firstPrefixCharacter(); + fail(); + } + catch (UnsupportedOperationException e) + { + assertNotNull(e.getMessage()); + } + } + + private List<String[]> permute(String[] array, int startIndex) + { + final List<String[]> result = new ArrayList<>(); + result.add(array); + for (int i = startIndex + 1; i < array.length; i++) + { + final String[] copy = Arrays.copyOf(array, array.length); + final String aux = copy[startIndex]; + copy[startIndex] = copy[i]; + copy[i] = aux; + result.addAll(permute(copy, startIndex + 1)); + } + return result; + } + + @Test + public void testIterator() + { + final String[] strings = new String[]{"AXB*", "AXC*", "AYD", "AYE", "D"}; + for (final String[] strs : permute(strings, 0)) + { + final PrefixTree tree = PrefixTree.from(Arrays.asList(strs)); + testIterator(tree, strings); + } + } + + private void testIterator(PrefixTree tree, String[] strings) + { + assertNotNull(tree); + + final List<String> list = Streams.stream(tree).collect(Collectors.toList()); + assertEquals(Arrays.asList(strings), list); + + final Iterator<String> iterator = tree.iterator(); + assertTrue(iterator.hasNext()); + assertTrue(iterator.hasNext()); + for (final String str : strings) + { + assertEquals(str, iterator.next()); + } + assertFalse(iterator.hasNext()); + assertFalse(iterator.hasNext()); + + try + { + iterator.next(); + fail("An exception is expected"); + } + catch (NoSuchElementException e) + { + // do nothing + } + } +} \ No newline at end of file diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/accesscontrolprovider/RuleBased.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/accesscontrolprovider/RuleBased.js index 8b32916..e1d7575 100644 --- a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/accesscontrolprovider/RuleBased.js +++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/accesscontrolprovider/RuleBased.js @@ -114,12 +114,24 @@ define(["dojo/_base/declare", if (value) { markup = "<div class='keyValuePair'>"; - for (var key in value) + for (let key in value) { if (value.hasOwnProperty(key)) { - markup += "<div>" + entities.encode(String(key)) + "=" - + entities.encode(String(value[key])) + "</div>"; + const valueList = value[key]; + if (Array.isArray(valueList)) + { + for (let item of valueList) + { + markup += "<div>" + entities.encode(String(key)) + "=" + + entities.encode(String(item)) + "</div>"; + } + } + else + { + markup += "<div>" + entities.encode(String(key)) + "=" + + entities.encode(String(valueList)) + "</div>"; + } } } markup += "</div>" diff --git a/doc/java-broker/src/docbkx/security/Java-Broker-Security-AccessControlProviders.xml b/doc/java-broker/src/docbkx/security/Java-Broker-Security-AccessControlProviders.xml index 8b92dd2..e700e4a 100644 --- a/doc/java-broker/src/docbkx/security/Java-Broker-Security-AccessControlProviders.xml +++ b/doc/java-broker/src/docbkx/security/Java-Broker-Security-AccessControlProviders.xml @@ -130,10 +130,15 @@ ACL rules follow this syntax: </para> <programlisting> - ACL {permission} {<group-name>|<user-name>|ALL} {action|ALL} [object|ALL] [property="<property-value>"] + ACL {permission} {<group-name>|<user-name>|ALL} {action|ALL} [object|ALL] [property=<property-values>] </programlisting> <para> + The <property-values> can be a single value property="single value" or a list of comma separated values in brackets + property=["value1", "value2", "value3"]. If a property repeats then it will be interpreted as list of values, + for example name="n1" name="n2" name="n3" is interpreted as name=["n1", "n2", "n3"]. + </para> + <para> Comments may be introduced with the hash (#) character and are ignored. Long lines can be broken with the slash (\) character. </para> <programlisting> @@ -509,7 +514,7 @@ The following ACL rules are given to the Broker. </para> <example> - <title>Worked example 2 - Simple Messaging - Broker ACL rules</title> + <title>Worked example 2a - Simple Messaging - Broker ACL rules</title> <programlisting><![CDATA[ # This gives the operate permission to delete messages on all queues on all virtualhost ACL ALLOW operator ACCESS MANAGEMENT @@ -517,6 +522,14 @@ ACL ALLOW operator INVOKE QUEUE method_name="deleteMessages" ACL ALLOW operator INVOKE QUEUE method_name="getMessage*"]]> </programlisting> </example> + <example> + <title>Worked example 2b - Simple Messaging - Broker ACL rules with multi-value property</title> + <programlisting><![CDATA[ +# This gives the operate permission to delete messages on all queues on all virtualhost +ACL ALLOW operator ACCESS MANAGEMENT +ACL ALLOW operator INVOKE QUEUE method_name=["deleteMessages", "getMessage*"] ]]> + </programlisting> + </example> <para> And the following ACL rule-set is applied to the Virtualhost. The default outcome of the Access Control Provider must be <literal>DEFERED</literal>. This means that if a request for --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org