Skip to content

Commit

Permalink
Update interfaces to allow more customizability
Browse files Browse the repository at this point in the history
* pass clientId in Authentication
* pass username, password (if set in broker constructor), payload in Authorization
* pass clientId and username in PacketInterceptor
  • Loading branch information
davidepianca98 committed May 14, 2021
1 parent 7997eda commit a639b37
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 19 deletions.
37 changes: 34 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ repositories {
maven { url "https://jitpack.io" }
}
dependencies {
implementation 'com.github.davidepianca98.KMQTT:KMQTT-jvm:0.2.7'
implementation 'com.github.davidepianca98.KMQTT:kmqtt-jvm:0.2.7'
}
```

Expand Down Expand Up @@ -92,7 +92,7 @@ fun main() {
```kotlin
fun main() {
val broker = Broker(authentication = object : Authentication {
override fun authenticate(username: String?, password: UByteArray?): Boolean {
override fun authenticate(clientId: String, username: String?, password: UByteArray?): Boolean {
// TODO Implement your authentication method
return username == "user" && password?.toByteArray()?.decodeToString() == "pass"
}
Expand All @@ -101,11 +101,33 @@ fun main() {
}
```

#### Authorization code example

```kotlin
fun main() {
val broker = Broker(authorization = object : Authorization {
override fun authorize(
clientId: String,
username: String?,
password: UByteArray?, // != null only if savePassword set to true in the broker constructor
topicName: String,
isSubscription: Boolean,
payload: UByteArray?
): Boolean {
// TODO Implement your authorization method
return topicName == "$clientId/topic"
}
})
broker.listen()
}
```

#### Message interceptor code example

```kotlin
fun main() {
val broker = Broker(packetInterceptor = object : PacketInterceptor {
override fun packetReceived(packet: MQTTPacket) {
override fun packetReceived(clientId: String, username: String?, packet: MQTTPacket) {
when(packet) {
is MQTTConnect -> println(packet.protocolName)
is MQTTPublish -> println(packet.topicName)
Expand All @@ -130,3 +152,12 @@ fun main() {
broker.listen()
}
```

#### Other advanced functionality

MQTT5 Enhanced Authentication: set the `enhancedAuthenticationProviders` Broker constructor parameter, implementing the
provider interface `EnhancedAuthenticationProvider`.

Session persistence: set the `persistence` Broker constructor parameter, implementing `Persistence` interface.

Bytes metrics: set the `bytesMetrics` Broker constructor parameter, implementing `BytesMetrics` interface.
1 change: 1 addition & 0 deletions src/commonMain/kotlin/mqtt/broker/Broker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Broker(
val authentication: Authentication? = null,
val enhancedAuthenticationProviders: Map<String, EnhancedAuthenticationProvider> = mapOf(),
val authorization: Authorization? = null,
val savePassword: Boolean = false,
val maximumSessionExpiryInterval: UInt = 0xFFFFFFFFu,
val receiveMaximum: UShort? = 1024u,
val maximumQos: Qos? = null,
Expand Down
31 changes: 22 additions & 9 deletions src/commonMain/kotlin/mqtt/broker/ClientConnection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class ClientConnection(
}

private var clientId: String? = null
private var username: String? = null
private var password: UByteArray? = null
private var session: Session? = null

// Client connection state
Expand Down Expand Up @@ -169,7 +171,7 @@ class ClientConnection(
else -> throw MQTTException(ReasonCode.PROTOCOL_ERROR)
}
}
broker.packetInterceptor?.packetReceived(packet)
broker.packetInterceptor?.packetReceived(clientId!!, username, packet)
}

/**
Expand Down Expand Up @@ -218,7 +220,7 @@ class ClientConnection(
private fun handleAuthentication(packet: MQTTConnect) {
if (broker.authentication != null) {
if (packet.userName != null || packet.password != null) {
if (!broker.authentication.authenticate(packet.userName, packet.password)) {
if (!broker.authentication.authenticate(clientId!!, packet.userName, packet.password)) {
throw MQTTException(ReasonCode.NOT_AUTHORIZED)
}
} else {
Expand Down Expand Up @@ -272,16 +274,20 @@ class ClientConnection(

private fun handleConnect(packet: MQTTConnect) {
connectPacket = packet
handleAuthentication(packet)

val clientId = if (packet.clientID.isEmpty()) {
val clientId = packet.clientID.ifEmpty {
if (packet is MQTT4Connect && !packet.connectFlags.cleanStart) {
writePacket(MQTT4Connack(ConnectAcknowledgeFlags(false), ConnectReturnCode.IDENTIFIER_REJECTED))
return
}
generateClientId()
} else packet.clientID
}
this.clientId = clientId
this.username = packet.userName
if (broker.savePassword) {
this.password = packet.password
}
handleAuthentication(packet)

if (packet is MQTT5Connect && packet.properties.authenticationMethod != null) {
packet.properties.authenticationMethod?.let { authenticationMethod ->
Expand Down Expand Up @@ -463,8 +469,15 @@ class ClientConnection(
packetsReceivedBeforeConnack.clear()
}

private fun checkAuthorization(topicName: String, isSubscription: Boolean): Boolean {
return broker.authorization?.authorize(clientId!!, topicName, isSubscription) != false
private fun checkAuthorization(topicName: String, isSubscription: Boolean, payload: UByteArray?): Boolean {
return broker.authorization?.authorize(
clientId!!,
username,
password,
topicName,
isSubscription,
payload
) != false
}

private fun handlePublish(packet: MQTTPublish) {
Expand All @@ -476,7 +489,7 @@ class ClientConnection(
// Handle topic alias
val topic = getTopicOrAlias(packet)

if (!checkAuthorization(topic, false))
if (!checkAuthorization(topic, false, packet.payload))
throw MQTTException(ReasonCode.NOT_AUTHORIZED)

if (packet.qos > broker.maximumQos ?: Qos.EXACTLY_ONCE) {
Expand Down Expand Up @@ -687,7 +700,7 @@ class ClientConnection(

val retainedMessagesList = mutableListOf<MQTTPublish>()
val reasonCodes = packet.subscriptions.map { subscription ->
if (!checkAuthorization(subscription.topicFilter, true))
if (!checkAuthorization(subscription.topicFilter, true, null))
return@map ReasonCode.NOT_AUTHORIZED

if (!subscription.matchTopicFilter.isValidTopic())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@ package mqtt.broker.interfaces

interface Authentication {

fun authenticate(username: String?, password: UByteArray?): Boolean
/**
* Checks if the client is allowed to connect to the broker
* @param clientId the MQTT clientId assigned to the client
* @param username the MQTT username provided in the CONNECT packet, if present, null otherwise
* @param password the MQTT password provided in the CONNECT packet, if present, null otherwise
*/
fun authenticate(clientId: String, username: String?, password: UByteArray?): Boolean
}
19 changes: 18 additions & 1 deletion src/commonMain/kotlin/mqtt/broker/interfaces/Authorization.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,22 @@ package mqtt.broker.interfaces

interface Authorization {

fun authorize(clientId: String, topicName: String, isSubscription: Boolean): Boolean
/**
* Checks if the client is allowed to do the specified operation
* @param clientId the MQTT clientId assigned to the client
* @param username the MQTT username provided in the CONNECT packet, if present, null otherwise
* @param password the MQTT password provided in the CONNECT packet, if present and broker parameter savePassword=true, null otherwise
* @param topicName the topic of the PUBLISH or SUBSCRIBE packet
* @param isSubscription true if the packet received is a SUBSCRIBE, false if it's a PUBLISH
* @param payload the content of the PUBLISH message, present only if isSubscription is false
* @return true if the client is allowed to publish or subscribe, false otherwise
*/
fun authorize(
clientId: String,
username: String?,
password: UByteArray?,
topicName: String,
isSubscription: Boolean,
payload: UByteArray?
): Boolean
}
10 changes: 10 additions & 0 deletions src/commonMain/kotlin/mqtt/broker/interfaces/BytesMetrics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ package mqtt.broker.interfaces

interface BytesMetrics {

/**
* Called when a client receives a packet
* @param clientId the clientId of the client that received the packet
* @param bytesCount the size of the received packet
*/
fun received(clientId: String, bytesCount: Long)

/**
* Called when a client sends a packet
* @param clientId the clientId of the client that sent the packet
* @param bytesCount the size of the sent packet
*/
fun sent(clientId: String, bytesCount: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface EnhancedAuthenticationProvider {
* Gets called upon reception of a CONNECT packet with the Authentication Method property set or upon reception
* of an AUTH packet
* @param clientId the requested or assigned Client ID if none is given by the client
* @param authenticationData the Authentication Data received if present in the received packet
* @param authenticationData the Authentication Data received, if present, in the received packet
* @param result function to call to continue the authentication or to set it as complete
*/
fun authReceived(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@ import mqtt.packets.MQTTPacket

interface PacketInterceptor {

fun packetReceived(packet: MQTTPacket)
/**
* Called when a packet is received from a client
* @param clientId the clientId assigned to the MQTT client that sent the packet
* @param username the MQTT username provided in the CONNECT packet, if present, null otherwise
* @param packet the MQTT packet sent by the client
*/
fun packetReceived(clientId: String, username: String?, packet: MQTTPacket)
}
9 changes: 8 additions & 1 deletion src/jvmTest/kotlin/RunLocal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import mqtt.broker.interfaces.Authorization

fun main() {
Broker(serverKeepAlive = 60, authorization = object : Authorization {
override fun authorize(clientId: String, topicName: String, isSubscription: Boolean): Boolean {
override fun authorize(
clientId: String,
username: String?,
password: UByteArray?,
topicName: String,
isSubscription: Boolean,
payload: UByteArray?
): Boolean {
return topicName != "test/nosubscribe"
}
}).listen()
Expand Down
4 changes: 2 additions & 2 deletions src/jvmTest/kotlin/integration/AuthenticationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class AuthenticationTest {
@Test
fun testSimpleAuthentication() {
val broker = Broker(authentication = object : Authentication {
override fun authenticate(username: String?, password: UByteArray?): Boolean {
override fun authenticate(clientId: String, username: String?, password: UByteArray?): Boolean {
return username == "user" && password?.toByteArray()?.decodeToString() == "pass"
}
})
Expand All @@ -58,7 +58,7 @@ class AuthenticationTest {
@Test(expected = Mqtt5ConnAckException::class)
fun testSimpleAuthenticationFailure() {
val broker = Broker(authentication = object : Authentication {
override fun authenticate(username: String?, password: UByteArray?): Boolean {
override fun authenticate(clientId: String, username: String?, password: UByteArray?): Boolean {
return username == "user" && password?.toByteArray()?.decodeToString() == "pass"
}
})
Expand Down

0 comments on commit a639b37

Please sign in to comment.