com.hivemq.extensions.handler.PluginAuthorizerServiceImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hivemq-community-edition-embedded Show documentation
Show all versions of hivemq-community-edition-embedded Show documentation
HiveMQ CE is a Java-based open source MQTT broker that fully supports MQTT 3.x and MQTT 5
The newest version!
/*
* Copyright 2019-present HiveMQ GmbH
*
* Licensed 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 com.hivemq.extensions.handler;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.hivemq.bootstrap.ClientConnection;
import com.hivemq.bootstrap.ClientConnectionContext;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.auth.parameter.AuthorizerProviderInput;
import com.hivemq.extension.sdk.api.client.parameter.ServerInformation;
import com.hivemq.extension.sdk.api.services.auth.provider.AuthorizerProvider;
import com.hivemq.extensions.ExtensionPriorityComparator;
import com.hivemq.extensions.HiveMQExtensions;
import com.hivemq.extensions.auth.parameter.AuthorizerProviderInputImpl;
import com.hivemq.extensions.auth.parameter.PublishAuthorizerInputImpl;
import com.hivemq.extensions.auth.parameter.PublishAuthorizerOutputImpl;
import com.hivemq.extensions.auth.parameter.SubscriptionAuthorizerInputImpl;
import com.hivemq.extensions.auth.parameter.SubscriptionAuthorizerOutputImpl;
import com.hivemq.extensions.client.ClientAuthorizers;
import com.hivemq.extensions.client.ClientAuthorizersImpl;
import com.hivemq.extensions.executor.PluginOutPutAsyncer;
import com.hivemq.extensions.executor.PluginTaskExecutorService;
import com.hivemq.extensions.handler.tasks.AllTopicsProcessedTask;
import com.hivemq.extensions.handler.tasks.PublishAuthorizationProcessedTask;
import com.hivemq.extensions.handler.tasks.PublishAuthorizerContext;
import com.hivemq.extensions.handler.tasks.PublishAuthorizerResult;
import com.hivemq.extensions.handler.tasks.PublishAuthorizerTask;
import com.hivemq.extensions.handler.tasks.SubscriptionAuthorizerContext;
import com.hivemq.extensions.handler.tasks.SubscriptionAuthorizerTask;
import com.hivemq.extensions.handler.tasks.WillPublishAuthorizationProcessedTask;
import com.hivemq.extensions.packets.general.UserPropertiesImpl;
import com.hivemq.extensions.services.auth.Authorizers;
import com.hivemq.mqtt.handler.disconnect.MqttServerDisconnector;
import com.hivemq.mqtt.handler.publish.IncomingPublishService;
import com.hivemq.mqtt.handler.subscribe.IncomingSubscribeService;
import com.hivemq.mqtt.message.connect.CONNECT;
import com.hivemq.mqtt.message.publish.PUBLISH;
import com.hivemq.mqtt.message.reason.Mqtt5DisconnectReasonCode;
import com.hivemq.mqtt.message.subscribe.SUBSCRIBE;
import com.hivemq.mqtt.message.subscribe.Topic;
import com.hivemq.util.Topics;
import io.netty.channel.ChannelHandlerContext;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.hivemq.configuration.service.InternalConfigurations.MQTT_ALLOW_DOLLAR_TOPICS;
/**
* @author Florian Limpöck
* @since 4.1.0
*/
@Singleton
public class PluginAuthorizerServiceImpl implements PluginAuthorizerService {
private final @NotNull Authorizers authorizers;
private final @NotNull PluginOutPutAsyncer asyncer;
private final @NotNull PluginTaskExecutorService pluginTaskExecutorService;
private final @NotNull ServerInformation serverInformation;
private final @NotNull MqttServerDisconnector mqttServerDisconnector;
private final @NotNull ExtensionPriorityComparator extensionPriorityComparator;
private final @NotNull IncomingPublishService incomingPublishService;
private final @NotNull IncomingSubscribeService incomingSubscribeService;
private final boolean allowDollarTopics;
@Inject
public PluginAuthorizerServiceImpl(
final @NotNull Authorizers authorizers,
final @NotNull PluginOutPutAsyncer asyncer,
final @NotNull PluginTaskExecutorService pluginTaskExecutorService,
final @NotNull ServerInformation serverInformation,
final @NotNull HiveMQExtensions hiveMQExtensions,
final @NotNull MqttServerDisconnector mqttServerDisconnector,
final @NotNull IncomingPublishService incomingPublishService,
final @NotNull IncomingSubscribeService incomingSubscribeService) {
this.authorizers = authorizers;
this.asyncer = asyncer;
this.pluginTaskExecutorService = pluginTaskExecutorService;
this.serverInformation = serverInformation;
this.incomingPublishService = incomingPublishService;
this.mqttServerDisconnector = mqttServerDisconnector;
this.extensionPriorityComparator = new ExtensionPriorityComparator(hiveMQExtensions);
this.incomingSubscribeService = incomingSubscribeService;
this.allowDollarTopics = MQTT_ALLOW_DOLLAR_TOPICS.get();
}
public void authorizePublish(final @NotNull ChannelHandlerContext ctx, final @NotNull PUBLISH msg) {
//We first check if the topic is allowed to be published
if (!Topics.isValidTopicToPublish(msg.getTopic())) {
disconnectWithReasonCode(ctx, "an invalid topic ('" + msg.getTopic() + "')", "an invalid topic");
return;
}
// if $ topics are not allowed
if (!allowDollarTopics && Topics.isDollarTopic(msg.getTopic())) {
final String reason = "a topic that starts with '$'";
disconnectWithReasonCode(ctx, reason + " ('" + msg.getTopic() + "')", reason);
return;
}
final String clientId = ClientConnection.of(ctx.channel()).getClientId();
if (clientId == null) {
//we must process the msg in every case !
incomingPublishService.processPublish(ctx, msg, null);
return;
}
if (!authorizers.areAuthorizersAvailable()) {
incomingPublishService.processPublish(ctx, msg, null);
return;
}
final Map providerMap = authorizers.getAuthorizerProviderMap();
if (providerMap.isEmpty()) {
incomingPublishService.processPublish(ctx, msg, null);
return;
}
final ClientAuthorizers clientAuthorizers = getClientAuthorizers(ctx);
final AuthorizerProviderInput authorizerProviderInput =
new AuthorizerProviderInputImpl(ctx.channel(), serverInformation, clientId);
final PublishAuthorizerInputImpl input = new PublishAuthorizerInputImpl(msg, ctx.channel(), clientId);
final PublishAuthorizerOutputImpl output = new PublishAuthorizerOutputImpl(asyncer);
final SettableFuture publishProcessedFuture = executePublishAuthorizer(clientId,
providerMap,
clientAuthorizers,
authorizerProviderInput,
input,
output,
ctx);
Futures.addCallback(publishProcessedFuture,
new PublishAuthorizationProcessedTask(msg, ctx, mqttServerDisconnector, incomingPublishService),
MoreExecutors.directExecutor());
}
public void authorizeWillPublish(final @NotNull ChannelHandlerContext ctx, final @NotNull CONNECT connect) {
final String clientId = ClientConnectionContext.of(ctx.channel()).getClientId();
if (clientId == null || !ctx.channel().isActive()) {
//no more processing needed, client is already disconnected
return;
}
if (!authorizers.areAuthorizersAvailable() || connect.getWillPublish() == null) {
ctx.pipeline()
.fireUserEventTriggered(new AuthorizeWillResultEvent(connect,
new PublishAuthorizerResult(null, null, false)));
return;
}
final Map providerMap = authorizers.getAuthorizerProviderMap();
if (providerMap.isEmpty()) {
ctx.pipeline()
.fireUserEventTriggered(new AuthorizeWillResultEvent(connect,
new PublishAuthorizerResult(null, null, false)));
return;
}
final ClientAuthorizers clientAuthorizers = getClientAuthorizers(ctx);
final AuthorizerProviderInput authorizerProviderInput =
new AuthorizerProviderInputImpl(ctx.channel(), serverInformation, clientId);
final PublishAuthorizerInputImpl input =
new PublishAuthorizerInputImpl(connect.getWillPublish(), ctx.channel(), clientId);
final PublishAuthorizerOutputImpl output = new PublishAuthorizerOutputImpl(asyncer);
final SettableFuture publishProcessedFuture = executePublishAuthorizer(clientId,
providerMap,
clientAuthorizers,
authorizerProviderInput,
input,
output,
ctx);
Futures.addCallback(publishProcessedFuture,
new WillPublishAuthorizationProcessedTask(connect, ctx),
MoreExecutors.directExecutor());
}
private @NotNull SettableFuture executePublishAuthorizer(
final @NotNull String clientId,
final @NotNull Map providerMap,
final @NotNull ClientAuthorizers clientAuthorizers,
final @NotNull AuthorizerProviderInput authorizerProviderInput,
final @NotNull PublishAuthorizerInputImpl input,
final @NotNull PublishAuthorizerOutputImpl output,
final @NotNull ChannelHandlerContext ctx) {
final SettableFuture publishProcessedFuture = SettableFuture.create();
final PublishAuthorizerContext context =
new PublishAuthorizerContext(clientId, output, publishProcessedFuture, providerMap.size(), ctx);
for (final Map.Entry entry : providerMap.entrySet()) {
final PublishAuthorizerTask task = new PublishAuthorizerTask(entry.getValue(),
entry.getKey(),
authorizerProviderInput,
clientAuthorizers,
ctx);
pluginTaskExecutorService.handlePluginInOutTaskExecution(context, input, output, task);
}
return publishProcessedFuture;
}
public void authorizeSubscriptions(final @NotNull ChannelHandlerContext ctx, final @NotNull SUBSCRIBE msg) {
final ClientConnectionContext clientConnectionContext = ClientConnectionContext.of(ctx.channel());
final String clientId = clientConnectionContext.getClientId();
if (clientId == null || !ctx.channel().isActive()) {
//no more processing needed
return;
}
if (!authorizers.areAuthorizersAvailable()) {
incomingSubscribeService.processSubscribe(ctx, msg, false);
return;
}
final Map providerMap = authorizers.getAuthorizerProviderMap();
if (providerMap.isEmpty()) {
incomingSubscribeService.processSubscribe(ctx, msg, false);
return;
}
final ClientAuthorizers clientAuthorizers = getClientAuthorizers(ctx);
final List> listenableFutures = new ArrayList<>();
final AuthorizerProviderInput authorizerProviderInput =
new AuthorizerProviderInputImpl(ctx.channel(), serverInformation, clientId);
//every topic gets its own task per authorizer
for (final Topic topic : msg.getTopics()) {
final SubscriptionAuthorizerInputImpl input =
new SubscriptionAuthorizerInputImpl(UserPropertiesImpl.of(msg.getUserProperties().asList()),
topic,
ctx.channel(),
clientId);
final SubscriptionAuthorizerOutputImpl output = new SubscriptionAuthorizerOutputImpl(asyncer);
final SettableFuture topicProcessedFuture = SettableFuture.create();
listenableFutures.add(topicProcessedFuture);
final SubscriptionAuthorizerContext context =
new SubscriptionAuthorizerContext(clientId, output, topicProcessedFuture, providerMap.size());
for (final Map.Entry entry : providerMap.entrySet()) {
final SubscriptionAuthorizerTask task = new SubscriptionAuthorizerTask(entry.getValue(),
entry.getKey(),
authorizerProviderInput,
clientAuthorizers);
pluginTaskExecutorService.handlePluginInOutTaskExecution(context, input, output, task);
}
}
final AllTopicsProcessedTask allTopicsProcessedTask = new AllTopicsProcessedTask(msg,
listenableFutures,
ctx,
mqttServerDisconnector,
incomingSubscribeService);
Futures.whenAllComplete(listenableFutures).run(allTopicsProcessedTask, MoreExecutors.directExecutor());
}
private @NotNull ClientAuthorizers getClientAuthorizers(final @NotNull ChannelHandlerContext ctx) {
final ClientConnectionContext clientConnectionContext = ClientConnectionContext.of(ctx.channel());
if (clientConnectionContext.getExtensionClientAuthorizers() == null) {
clientConnectionContext.setExtensionClientAuthorizers(new ClientAuthorizersImpl(extensionPriorityComparator));
}
return clientConnectionContext.getExtensionClientAuthorizers();
}
private void disconnectWithReasonCode(
final @NotNull ChannelHandlerContext ctx,
@NotNull final String logReason,
final @NotNull String reasonString) {
if (ctx.channel().isActive()) {
final String logMessage =
"Client (IP: {}) sent PUBLISH for " + logReason + ". This is not allowed. Disconnecting client.";
final String reasonMessage = "Sent PUBLISH for " + reasonString;
mqttServerDisconnector.disconnect(ctx.channel(),
logMessage,
reasonMessage,
Mqtt5DisconnectReasonCode.PROTOCOL_ERROR,
reasonMessage);
ctx.close();
}
}
public static class AuthorizeWillResultEvent {
private final @NotNull CONNECT connect;
private final @NotNull PublishAuthorizerResult result;
public AuthorizeWillResultEvent(final @NotNull CONNECT connect, final @NotNull PublishAuthorizerResult result) {
this.connect = connect;
this.result = result;
}
@NotNull
public CONNECT getConnect() {
return connect;
}
@NotNull
public PublishAuthorizerResult getResult() {
return result;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy