diff --git a/README.adoc b/README.adoc index 100dc23..a44babb 100644 --- a/README.adoc +++ b/README.adoc @@ -3,7 +3,7 @@ == HiveMQ Deny Wildcard Subscription Extension *Extension Type*: Security -*Version*: 4.0.0 +*Version*: 4.1.1 *License*: Apache License 2.0 @@ -21,7 +21,8 @@ This extension denies any subscription to the root wildcard topic. In general, r No configuration needed, works out of the box. === First Steps -After the extension is installed start HiveMQ. Now no client is allowed to subscribe to `#`. +After the extension is installed start HiveMQ. Now no client is allowed to subscribe to `#`, `+/#`, `$share/group/#` +or any other topic filter that would represent a root wildcard. ==== Need help? diff --git a/pom.xml b/pom.xml index 4ee3976..6f75ec3 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.hivemq.extension hivemq-deny-wildcard-extension - 4.1.0 + 4.1.1 HiveMQ Extension to deny top level wildcard subscription 2018 diff --git a/src/main/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizer.java b/src/main/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizer.java index e4fc0ee..0b751c9 100644 --- a/src/main/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizer.java +++ b/src/main/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizer.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -41,7 +42,8 @@ public class DenyWildcardAuthorizer implements SubscriptionAuthorizer { public static final DenyWildcardAuthorizer INSTANCE = new DenyWildcardAuthorizer(); - public static final String REASON_STRING = "Root wildcard subscription not supported."; + public static final String REASON_STRING = "Root wildcard subscriptions are not supported."; + private static final Pattern SHARED_SUBSCRIPTION_PATTERN = Pattern.compile("\\$share(/.*?/(.*))"); private DenyWildcardAuthorizer() { } @@ -52,6 +54,18 @@ private DenyWildcardAuthorizer() { @Override public void authorizeSubscribe(@NotNull final SubscriptionAuthorizerInput subscriptionAuthorizerInput, @NotNull final SubscriptionAuthorizerOutput subscriptionAuthorizerOutput) { final String topicFilter = subscriptionAuthorizerInput.getSubscription().getTopicFilter(); + + if (topicFilter.startsWith("$share/")) { + final Matcher matcher = SHARED_SUBSCRIPTION_PATTERN.matcher(topicFilter); + if (matcher.matches()) { + final String subscriptionTopic = matcher.group(2); + if (StringUtils.containsOnly(subscriptionTopic, WILDCARD_CHARS)) { + logger.debug("Client {} tried to subscribe to an denied shared root wildcard topic filter '{}'", subscriptionAuthorizerInput.getClientInformation().getClientId(), topicFilter); + subscriptionAuthorizerOutput.failAuthorization(SubackReasonCode.NOT_AUTHORIZED, REASON_STRING); + } + } + } + if (StringUtils.containsOnly(topicFilter, WILDCARD_CHARS)) { logger.debug("Client {} tried to subscribe to an denied root wildcard topic filter '{}'", subscriptionAuthorizerInput.getClientInformation().getClientId(), topicFilter); subscriptionAuthorizerOutput.failAuthorization(SubackReasonCode.NOT_AUTHORIZED, REASON_STRING); diff --git a/src/test/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizerTest.java b/src/test/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizerTest.java index 486c1d7..29fcb76 100644 --- a/src/test/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizerTest.java +++ b/src/test/java/com/hivemq/extension/callbacks/DenyWildcardAuthorizerTest.java @@ -63,61 +63,62 @@ public void test_denied_hashtag() { } @Test - public void test_denied_plus() { - when(input.getSubscription().getTopicFilter()).thenReturn("+"); + public void test_denied_shared_hash() { + when(input.getSubscription().getTopicFilter()).thenReturn("$share/group/#"); DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); verify(output).failAuthorization(SubackReasonCode.NOT_AUTHORIZED, DenyWildcardAuthorizer.REASON_STRING); } @Test - public void test_denied_plus_slash() { - when(input.getSubscription().getTopicFilter()).thenReturn("+/"); + public void test_denied_hash() { + when(input.getSubscription().getTopicFilter()).thenReturn("/#"); DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); verify(output).failAuthorization(SubackReasonCode.NOT_AUTHORIZED, DenyWildcardAuthorizer.REASON_STRING); } @Test - public void test_denied_hashtag_slash() { - when(input.getSubscription().getTopicFilter()).thenReturn("#/"); + public void test_denied_shared_slash_hash() { + when(input.getSubscription().getTopicFilter()).thenReturn("$share/group//#"); DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); verify(output).failAuthorization(SubackReasonCode.NOT_AUTHORIZED, DenyWildcardAuthorizer.REASON_STRING); } @Test - public void test_denied_hashtag_plus() { - when(input.getSubscription().getTopicFilter()).thenReturn("#/+"); + public void test_denied_plus_hash() { + when(input.getSubscription().getTopicFilter()).thenReturn("+/#"); DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); verify(output).failAuthorization(SubackReasonCode.NOT_AUTHORIZED, DenyWildcardAuthorizer.REASON_STRING); } @Test - public void test_denied_plus_hashtag() { - when(input.getSubscription().getTopicFilter()).thenReturn("+/#"); + public void test_denied_shared_plus_hash() { + when(input.getSubscription().getTopicFilter()).thenReturn("$share/group/+/#"); DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); verify(output).failAuthorization(SubackReasonCode.NOT_AUTHORIZED, DenyWildcardAuthorizer.REASON_STRING); } @Test - public void test_denied_slash_plus() { - when(input.getSubscription().getTopicFilter()).thenReturn("/+"); + public void test_denied_plus_plus() { + when(input.getSubscription().getTopicFilter()).thenReturn("+/+"); DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); verify(output).failAuthorization(SubackReasonCode.NOT_AUTHORIZED, DenyWildcardAuthorizer.REASON_STRING); } @Test - public void test_denied_slash_hashtag() { - when(input.getSubscription().getTopicFilter()).thenReturn("/#"); + public void test_denied_shared_plus_plus() { + when(input.getSubscription().getTopicFilter()).thenReturn("$share/group/+/+"); DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); verify(output).failAuthorization(SubackReasonCode.NOT_AUTHORIZED, DenyWildcardAuthorizer.REASON_STRING); } + @Test public void test_success() { when(input.getSubscription().getTopicFilter()).thenReturn("topic"); @@ -134,6 +135,30 @@ public void test_success_non_root_hashtag() { verify(output).authorizeSuccessfully(); } + @Test + public void test_success_shared_non_root_wildcard() { + when(input.getSubscription().getTopicFilter()).thenReturn("$share/group/topic/#"); + DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); + + verify(output).authorizeSuccessfully(); + } + + @Test + public void test_success_non_root_plus_wildcard() { + when(input.getSubscription().getTopicFilter()).thenReturn("+/topic/#"); + DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); + + verify(output).authorizeSuccessfully(); + } + + @Test + public void test_success_shared_non_root_plus_wildcard() { + when(input.getSubscription().getTopicFilter()).thenReturn("$share/group/+/topic/#"); + DenyWildcardAuthorizer.INSTANCE.authorizeSubscribe(input, output); + + verify(output).authorizeSuccessfully(); + } + @Test public void test_success_non_root_plus() { when(input.getSubscription().getTopicFilter()).thenReturn("topic/+");