This closes #4197
This commit is contained in:
commit
4b791b502d
|
@ -45,6 +45,7 @@ import io.netty.util.CharsetUtil;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
|
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
|
||||||
import org.apache.activemq.artemis.api.core.Pair;
|
import org.apache.activemq.artemis.api.core.Pair;
|
||||||
|
import org.apache.activemq.artemis.core.protocol.mqtt.exceptions.InvalidClientIdException;
|
||||||
import org.apache.activemq.artemis.core.protocol.mqtt.exceptions.DisconnectException;
|
import org.apache.activemq.artemis.core.protocol.mqtt.exceptions.DisconnectException;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
import org.apache.activemq.artemis.logs.AuditLogger;
|
import org.apache.activemq.artemis.logs.AuditLogger;
|
||||||
|
@ -241,8 +242,14 @@ public class MQTTProtocolHandler extends ChannelInboundHandlerAdapter {
|
||||||
*/
|
*/
|
||||||
String password = connect.payload().passwordInBytes() == null ? null : new String(connect.payload().passwordInBytes(), CharsetUtil.UTF_8);
|
String password = connect.payload().passwordInBytes() == null ? null : new String(connect.payload().passwordInBytes(), CharsetUtil.UTF_8);
|
||||||
String username = connect.payload().userName();
|
String username = connect.payload().userName();
|
||||||
Pair<Boolean, String> validationData = validateUser(username, password);
|
Pair<Boolean, String> validationData = null;
|
||||||
if (!validationData.getA()) {
|
try {
|
||||||
|
validationData = validateUser(username, password);
|
||||||
|
if (!validationData.getA()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (InvalidClientIdException e) {
|
||||||
|
handleInvalidClientId();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,15 +494,19 @@ public class MQTTProtocolHandler extends ChannelInboundHandlerAdapter {
|
||||||
session.getConnection().setClientIdAssignedByBroker(true);
|
session.getConnection().setClientIdAssignedByBroker(true);
|
||||||
} else {
|
} else {
|
||||||
// [MQTT-3.1.3-8] Return ID rejected and disconnect if clean session = false and client id is null
|
// [MQTT-3.1.3-8] Return ID rejected and disconnect if clean session = false and client id is null
|
||||||
if (session.getVersion() == MQTTVersion.MQTT_5) {
|
return handleInvalidClientId();
|
||||||
session.getProtocolHandler().sendConnack(MQTTReasonCodes.CLIENT_IDENTIFIER_NOT_VALID);
|
|
||||||
} else {
|
|
||||||
session.getProtocolHandler().sendConnack(MQTTReasonCodes.IDENTIFIER_REJECTED_3);
|
|
||||||
}
|
|
||||||
disconnect(true);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean handleInvalidClientId() {
|
||||||
|
if (session.getVersion() == MQTTVersion.MQTT_5) {
|
||||||
|
session.getProtocolHandler().sendConnack(MQTTReasonCodes.CLIENT_IDENTIFIER_NOT_VALID);
|
||||||
|
} else {
|
||||||
|
session.getProtocolHandler().sendConnack(MQTTReasonCodes.IDENTIFIER_REJECTED_3);
|
||||||
|
}
|
||||||
|
disconnect(true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.artemis.core.protocol.mqtt.exceptions;
|
||||||
|
|
||||||
|
public class InvalidClientIdException extends RuntimeException {
|
||||||
|
|
||||||
|
}
|
|
@ -100,6 +100,20 @@ UTF-8 strings and print them up to 256 characters. Payload logging is limited
|
||||||
to avoid filling the logs with potentially hundreds of megabytes of unhelpful
|
to avoid filling the logs with potentially hundreds of megabytes of unhelpful
|
||||||
information.
|
information.
|
||||||
|
|
||||||
|
## Custom Client ID Handling
|
||||||
|
|
||||||
|
The client ID used by an MQTT application is very important as it uniquely
|
||||||
|
identifies the application. In some situations broker administrators may want
|
||||||
|
to perform extra validation or even modify incoming client IDs to support
|
||||||
|
specific use-cases. This is possible by implementing a custom security manager
|
||||||
|
as demonstrated in the `security-manager` example shipped with the broker.
|
||||||
|
|
||||||
|
The simplest implementation is a "wrapper" just like the `security-manager`
|
||||||
|
example uses. In the `authenticate` method you can modify the client ID using
|
||||||
|
`setClientId()` on the `org.apache.activemq.artemis.spi.core.protocol.RemotingConnection`
|
||||||
|
that is passed in. If you perform some custom validation of the client ID you
|
||||||
|
can reject the client ID by throwing a `org.apache.activemq.artemis.core.protocol.mqtt.exceptions.InvalidClientIdException`.
|
||||||
|
|
||||||
## Wildcard subscriptions
|
## Wildcard subscriptions
|
||||||
|
|
||||||
MQTT addresses are hierarchical much like a file system, and they use a special
|
MQTT addresses are hierarchical much like a file system, and they use a special
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.apache.activemq.artemis.core.protocol.mqtt.MQTTProtocolManager;
|
import org.apache.activemq.artemis.core.protocol.mqtt.MQTTProtocolManager;
|
||||||
import org.apache.activemq.artemis.core.protocol.mqtt.MQTTSessionState;
|
import org.apache.activemq.artemis.core.protocol.mqtt.MQTTSessionState;
|
||||||
|
import org.apache.activemq.artemis.core.protocol.mqtt.exceptions.InvalidClientIdException;
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.AbstractAcceptor;
|
import org.apache.activemq.artemis.core.remoting.impl.AbstractAcceptor;
|
||||||
import org.apache.activemq.artemis.core.security.CheckType;
|
import org.apache.activemq.artemis.core.security.CheckType;
|
||||||
import org.apache.activemq.artemis.core.security.Role;
|
import org.apache.activemq.artemis.core.security.Role;
|
||||||
|
@ -33,11 +34,14 @@ import org.apache.activemq.artemis.tests.util.RandomUtil;
|
||||||
import org.apache.activemq.artemis.tests.util.Wait;
|
import org.apache.activemq.artemis.tests.util.Wait;
|
||||||
import org.fusesource.mqtt.client.BlockingConnection;
|
import org.fusesource.mqtt.client.BlockingConnection;
|
||||||
import org.fusesource.mqtt.client.MQTT;
|
import org.fusesource.mqtt.client.MQTT;
|
||||||
|
import org.fusesource.mqtt.client.MQTTException;
|
||||||
|
import org.fusesource.mqtt.codec.CONNACK;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class MQTTSecurityManagerTest extends MQTTTestSupport {
|
public class MQTTSecurityManagerTest extends MQTTTestSupport {
|
||||||
|
|
||||||
private String clientID = "new-" + RandomUtil.randomString();
|
private String clientID = "new-" + RandomUtil.randomString();
|
||||||
|
private boolean rejectClientId = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecurityEnabled() {
|
public boolean isSecurityEnabled() {
|
||||||
|
@ -53,6 +57,9 @@ public class MQTTSecurityManagerTest extends MQTTTestSupport {
|
||||||
String password,
|
String password,
|
||||||
RemotingConnection remotingConnection,
|
RemotingConnection remotingConnection,
|
||||||
String securityDomain) {
|
String securityDomain) {
|
||||||
|
if (rejectClientId) {
|
||||||
|
throw new InvalidClientIdException();
|
||||||
|
}
|
||||||
remotingConnection.setClientID(clientID);
|
remotingConnection.setClientID(clientID);
|
||||||
return new Subject();
|
return new Subject();
|
||||||
}
|
}
|
||||||
|
@ -148,4 +155,25 @@ public class MQTTSecurityManagerTest extends MQTTTestSupport {
|
||||||
if (connection1 != null && connection1.isConnected()) connection1.disconnect();
|
if (connection1 != null && connection1.isConnected()) connection1.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 30000)
|
||||||
|
public void testSecurityManagerRejectClientID() throws Exception {
|
||||||
|
rejectClientId = true;
|
||||||
|
BlockingConnection connection = null;
|
||||||
|
try {
|
||||||
|
MQTT mqtt = createMQTTConnection(RandomUtil.randomString(), true);
|
||||||
|
mqtt.setUserName(fullUser);
|
||||||
|
mqtt.setPassword(fullPass);
|
||||||
|
mqtt.setConnectAttemptsMax(1);
|
||||||
|
connection = mqtt.blockingConnection();
|
||||||
|
try {
|
||||||
|
connection.connect();
|
||||||
|
fail("Should have thrown exception");
|
||||||
|
} catch (MQTTException e) {
|
||||||
|
assertEquals(CONNACK.Code.CONNECTION_REFUSED_IDENTIFIER_REJECTED, e.connack.code());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (connection != null && connection.isConnected()) connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue