summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaurice Kalinowski <maurice.kalinowski@qt.io>2018-07-26 12:01:54 +0200
committerMaurice Kalinowski <maurice.kalinowski@qt.io>2018-08-02 09:15:55 +0000
commit4ffc9efc7ba30e0459a6653aac626c01a1f57173 (patch)
tree48fb58be9e8c7c24c772cd35eb64dc7622543d8f
parentbcc2a4e7b5a38e7314e0444138c8271ecbced9d0 (diff)
MQTT5: Introduce AUTH support
Task-number: QTPM-1455 Change-Id: I8eef500fe5eb0e8a36f449534c70e35487c04a7b Reviewed-by: hjk <hjk@qt.io>
-rw-r--r--src/mqtt/mqtt.pro2
-rw-r--r--src/mqtt/qmqttauthenticationproperties.cpp99
-rw-r--r--src/mqtt/qmqttauthenticationproperties.h73
-rw-r--r--src/mqtt/qmqttclient.cpp14
-rw-r--r--src/mqtt/qmqttclient.h5
-rw-r--r--src/mqtt/qmqttconnection.cpp150
-rw-r--r--src/mqtt/qmqttconnection_p.h4
-rw-r--r--src/mqtt/qmqttcontrolpacket_p.h1
-rw-r--r--tests/auto/qmqttclient/tst_qmqttclient.cpp30
9 files changed, 378 insertions, 0 deletions
diff --git a/src/mqtt/mqtt.pro b/src/mqtt/mqtt.pro
index 13efe74..adbfc54 100644
--- a/src/mqtt/mqtt.pro
+++ b/src/mqtt/mqtt.pro
@@ -8,6 +8,7 @@ QT += core-private
QMAKE_DOCS = $$PWD/doc/qtmqtt.qdocconf
PUBLIC_HEADERS += \
+ qmqttauthenticationproperties.h \
qmqttglobal.h \
qmqttclient.h \
qmqttconnectionproperties.h \
@@ -28,6 +29,7 @@ PRIVATE_HEADERS += \
qmqttsubscription_p.h
SOURCES += \
+ qmqttauthenticationproperties.cpp \
qmqttclient.cpp \
qmqttconnection.cpp \
qmqttconnectionproperties.cpp \
diff --git a/src/mqtt/qmqttauthenticationproperties.cpp b/src/mqtt/qmqttauthenticationproperties.cpp
new file mode 100644
index 0000000..778aa6b
--- /dev/null
+++ b/src/mqtt/qmqttauthenticationproperties.cpp
@@ -0,0 +1,99 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt module.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "qmqttauthenticationproperties.h"
+
+QT_BEGIN_NAMESPACE
+
+class QMqttAuthenticationPropertiesData : public QSharedData
+{
+public:
+ QString authenticationMethod;
+ QByteArray authenticationData;
+ QString reason;
+ QMqttUserProperties userProperties;
+};
+
+QMqttAuthenticationProperties::QMqttAuthenticationProperties() : data(new QMqttAuthenticationPropertiesData)
+{
+
+}
+
+QMqttAuthenticationProperties::QMqttAuthenticationProperties(const QMqttAuthenticationProperties &) = default;
+
+QMqttAuthenticationProperties &QMqttAuthenticationProperties::operator=(const QMqttAuthenticationProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+QMqttAuthenticationProperties::~QMqttAuthenticationProperties() = default;
+
+QString QMqttAuthenticationProperties::authenticationMethod() const
+{
+ return data->authenticationMethod;
+}
+
+void QMqttAuthenticationProperties::setAuthenticationMethod(const QString &method)
+{
+ data->authenticationMethod = method;
+}
+
+QByteArray QMqttAuthenticationProperties::authenticationData() const
+{
+ return data->authenticationData;
+}
+
+void QMqttAuthenticationProperties::setAuthenticationData(const QByteArray &adata)
+{
+ data->authenticationData = adata;
+}
+
+QString QMqttAuthenticationProperties::reason() const
+{
+ return data->reason;
+}
+
+void QMqttAuthenticationProperties::setReason(const QString &r)
+{
+ data->reason = r;
+}
+
+QMqttUserProperties QMqttAuthenticationProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+void QMqttAuthenticationProperties::setUserProperties(const QMqttUserProperties &user)
+{
+ data->userProperties = user;
+}
+
+QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttauthenticationproperties.h b/src/mqtt/qmqttauthenticationproperties.h
new file mode 100644
index 0000000..b587338
--- /dev/null
+++ b/src/mqtt/qmqttauthenticationproperties.h
@@ -0,0 +1,73 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt module.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef QMQTTAUTHENTICATIONPROPERTIES_H
+#define QMQTTAUTHENTICATIONPROPERTIES_H
+
+#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqtttype.h>
+
+#include <QtCore/QHash>
+#include <QtCore/QObject>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QString>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttAuthenticationPropertiesData;
+
+class Q_MQTT_EXPORT QMqttAuthenticationProperties
+{
+ Q_GADGET
+
+public:
+ QMqttAuthenticationProperties();
+ QMqttAuthenticationProperties(const QMqttAuthenticationProperties &);
+ QMqttAuthenticationProperties &operator=(const QMqttAuthenticationProperties &);
+ ~QMqttAuthenticationProperties();
+
+ QString authenticationMethod() const;
+ void setAuthenticationMethod(const QString &method);
+
+ QByteArray authenticationData() const;
+ void setAuthenticationData(const QByteArray &adata);
+
+ QString reason() const;
+ void setReason(const QString &r);
+
+ QMqttUserProperties userProperties() const;
+ void setUserProperties(const QMqttUserProperties &user);
+
+private:
+ QSharedDataPointer<QMqttAuthenticationPropertiesData> data;
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTAUTHENTICATIONPROPERTIES_H
diff --git a/src/mqtt/qmqttclient.cpp b/src/mqtt/qmqttclient.cpp
index 5bf862e..948ea9a 100644
--- a/src/mqtt/qmqttclient.cpp
+++ b/src/mqtt/qmqttclient.cpp
@@ -553,6 +553,20 @@ QMqttServerConnectionProperties QMqttClient::serverConnectionProperties() const
return d->m_serverConnectionProperties;
}
+void QMqttClient::authenticate(const QMqttAuthenticationProperties &prop)
+{
+ Q_D(QMqttClient);
+ if (protocolVersion() != QMqttClient::MQTT_5_0) {
+ qCDebug(lcMqttClient) << "Authentication is only supported on protocol level 5.";
+ return;
+ }
+ if (state() == QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Cannot send authentication request while disconnected.";
+ return;
+ }
+ d->m_connection.sendControlAuthenticate(prop);
+}
+
QMqttClient::ClientError QMqttClient::error() const
{
Q_D(const QMqttClient);
diff --git a/src/mqtt/qmqttclient.h b/src/mqtt/qmqttclient.h
index d0ec8a1..d462e35 100644
--- a/src/mqtt/qmqttclient.h
+++ b/src/mqtt/qmqttclient.h
@@ -31,6 +31,7 @@
#define QTMQTTCLIENT_H
#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqttauthenticationproperties.h>
#include <QtMqtt/qmqttconnectionproperties.h>
#include <QtMqtt/qmqttpublishproperties.h>
#include <QtMqtt/qmqttsubscription.h>
@@ -148,6 +149,8 @@ public:
QMqttLastWillProperties lastWillProperties() const;
QMqttServerConnectionProperties serverConnectionProperties() const;
+
+ void authenticate(const QMqttAuthenticationProperties &prop);
Q_SIGNALS:
void connected();
void disconnected();
@@ -172,6 +175,8 @@ Q_SIGNALS:
void willMessageChanged(QByteArray willMessage);
void willRetainChanged(bool willRetain);
+ void authenticationRequested(const QMqttAuthenticationProperties &p);
+ void authenticationFinished(const QMqttAuthenticationProperties &p);
public Q_SLOTS:
void setHostname(const QString &hostname);
void setPort(quint16 port);
diff --git a/src/mqtt/qmqttconnection.cpp b/src/mqtt/qmqttconnection.cpp
index 1337911..9d58959 100644
--- a/src/mqtt/qmqttconnection.cpp
+++ b/src/mqtt/qmqttconnection.cpp
@@ -81,6 +81,15 @@ QString QMqttConnection::readBufferTyped(qint64 *dataSize)
return QString::fromUtf8(reinterpret_cast<const char *>(readBuffer(size).constData()), size);
}
+template<>
+QByteArray QMqttConnection::readBufferTyped(qint64 *dataSize)
+{
+ const quint16 size = readBufferTyped<quint16>(dataSize);
+ if (dataSize)
+ *dataSize -= size;
+ return QByteArray(reinterpret_cast<const char *>(readBuffer(size).constData()), size);
+}
+
QMqttConnection::QMqttConnection(QObject *parent) : QObject(parent)
{
m_pingTimer.setSingleShot(false);
@@ -299,6 +308,36 @@ bool QMqttConnection::sendControlConnect()
return true;
}
+bool QMqttConnection::sendControlAuthenticate(const QMqttAuthenticationProperties &properties)
+{
+ qCDebug(lcMqttConnection) << Q_FUNC_INFO;
+
+ QMqttControlPacket packet(QMqttControlPacket::AUTH);
+
+ switch (m_clientPrivate->m_state) {
+ case QMqttClient::Disconnected:
+ qCDebug(lcMqttConnection) << "Using AUTH while disconnected.";
+ return false;
+ case QMqttClient::Connecting:
+ qCDebug(lcMqttConnection) << "AUTH while connecting, set continuation flag.";
+ packet.append(char(0x18));
+ break;
+ case QMqttClient::Connected:
+ qCDebug(lcMqttConnection) << "AUTH while connected, initiate re-authentication.";
+ packet.append(char(0x19));
+ break;
+ }
+
+ packet.appendRaw(writeAuthenticationProperties(properties));
+
+ if (!writePacketToTransport(packet)) {
+ qCDebug(lcMqttConnection) << "Could not write AUTH frame to transport.";
+ return false;
+ }
+
+ return true;
+}
+
qint32 QMqttConnection::sendControlPublish(const QMqttTopicName &topic,
const QByteArray &message,
quint8 qos,
@@ -647,6 +686,47 @@ QByteArray QMqttConnection::readBuffer(quint64 size)
return res;
}
+void QMqttConnection::readAuthProperties(QMqttAuthenticationProperties &properties)
+{
+ qint64 propertyLength = readVariableByteInteger();
+ m_missingData = 0;
+
+ QMqttUserProperties userProperties;
+ while (propertyLength > 0) {
+ quint8 propertyId = readBufferTyped<quint8>();
+ propertyLength--;
+
+ switch (propertyId) {
+ case 0x15: { //3.15.2.2.2 Authentication Method
+ const QString method = readBufferTyped<QString>(&propertyLength);
+ properties.setAuthenticationMethod(method);
+ break;
+ }
+ case 0x16: { // 3.15.2.2.3 Authentication Data
+ const QByteArray data = readBufferTyped<QByteArray>(&propertyLength);
+ properties.setAuthenticationData(data);
+ break;
+ }
+ case 0x1F: { // 3.15.2.2.4 Reason String
+ const QString reasonString = readBufferTyped<QString>(&propertyLength);
+ properties.setReason(reasonString);
+ break;
+ }
+ case 0x26: { // 3.15.2.2.5 User property
+ const QString propertyName = readBufferTyped<QString>(&propertyLength);
+ const QString propertyValue = readBufferTyped<QString>(&propertyLength);
+
+ userProperties.append(QMqttStringPair(propertyName, propertyValue));
+ break;
+ }
+ default:
+ qCDebug(lcMqttConnection) << "Unknown property id in AUTH:" << propertyId;
+ break;
+ }
+ }
+ properties.setUserProperties(userProperties);
+}
+
void QMqttConnection::readConnackProperties()
{
qint64 propertyLength = readVariableByteInteger();
@@ -1156,6 +1236,73 @@ QByteArray QMqttConnection::writeUnsubscriptionProperties(const QMqttUnsubscript
return packet.serializePayload();
}
+QByteArray QMqttConnection::writeAuthenticationProperties(const QMqttAuthenticationProperties &properties)
+{
+ QMqttControlPacket packet;
+
+ // 3.15.2.2.2
+ if (!properties.authenticationMethod().isEmpty()) {
+ packet.append(char(0x15));
+ packet.append(properties.authenticationMethod().toUtf8());
+ }
+ // 3.15.2.2.3
+ if (!properties.authenticationData().isEmpty()) {
+ packet.append(char(0x16));
+ packet.append(properties.authenticationData());
+ }
+
+ // 3.15.2.2.4
+ if (!properties.reason().isEmpty()) {
+ packet.append(char(0x1F));
+ packet.append(properties.reason().toUtf8());
+ }
+
+ // 3.15.2.2.5
+ auto userProperties = properties.userProperties();
+ if (!userProperties.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Unsubscription Properties: specify user properties";
+ for (const auto &prop : userProperties) {
+ packet.append(char(0x26));
+ packet.append(prop.name().toUtf8());
+ packet.append(prop.value().toUtf8());
+ }
+ }
+
+ return packet.serializePayload();
+}
+
+void QMqttConnection::finalize_auth()
+{
+ qCDebug(lcMqttConnectionVerbose) << "Finalize AUTH";
+
+ const quint8 authReason = readBufferTyped<quint8>();
+ m_missingData--;
+ QMqttAuthenticationProperties authProperties;
+ // 3.15.2.1 - The Reason Code and Property Length can be omitted if the Reason Code
+ // is 0x00 (Success) and there are no Properties. In this case the AUTH has a
+ // Remaining Length of 0.
+ if (m_missingData == 0 && authReason != 0) {
+ qCDebug(lcMqttConnection) << "Received non success AUTH without properties.";
+ closeConnection(QMqttClient::ProtocolViolation);
+ return;
+ } else if (m_missingData > 0)
+ readAuthProperties(authProperties);
+
+ switch (authReason) {
+ case 0x00: // Success
+ emit m_clientPrivate->m_client->authenticationFinished(authProperties);
+ break;
+ case 0x18: // Continue Authentication
+ case 0x19: // Re-authenticate
+ emit m_clientPrivate->m_client->authenticationRequested(authProperties);
+ break;
+ default:
+ qCDebug(lcMqttConnection) << "Received illegal AUTH reason code:" << authReason;
+ closeConnection(QMqttClient::ProtocolViolation);
+ break;
+ }
+}
+
void QMqttConnection::finalize_connack()
{
qCDebug(lcMqttConnectionVerbose) << "Finalize CONNACK";
@@ -1413,6 +1560,9 @@ bool QMqttConnection::processDataHelper()
return false;
switch (m_currentPacket & 0xF0) {
+ case QMqttControlPacket::AUTH:
+ finalize_auth();
+ break;
case QMqttControlPacket::CONNACK:
finalize_connack();
break;
diff --git a/src/mqtt/qmqttconnection_p.h b/src/mqtt/qmqttconnection_p.h
index 6eee164..b8da93a 100644
--- a/src/mqtt/qmqttconnection_p.h
+++ b/src/mqtt/qmqttconnection_p.h
@@ -77,6 +77,7 @@ public:
bool ensureTransportOpen(const QString &sslPeerName = QString());
bool sendControlConnect();
+ bool sendControlAuthenticate(const QMqttAuthenticationProperties &properties);
qint32 sendControlPublish(const QMqttTopicName &topic, const QByteArray &message, quint8 qos = 0, bool retain = false,
const QMqttPublishProperties &properties = QMqttPublishProperties());
bool sendControlPublishAcknowledge(quint16 id);
@@ -107,6 +108,7 @@ public:
QMqttClientPrivate *m_clientPrivate{nullptr};
private:
Q_DISABLE_COPY(QMqttConnection)
+ void finalize_auth();
void finalize_connack();
void finalize_suback();
void finalize_unsuback();
@@ -118,6 +120,7 @@ private:
bool processDataHelper();
void readBuffer(char *data, quint64 size);
qint32 readVariableByteInteger(qint32 *byteCount = nullptr);
+ void readAuthProperties(QMqttAuthenticationProperties &properties);
void readConnackProperties();
void readPublishProperties(QMqttPublishProperties &properties);
void readSubscriptionProperties(QMqttSubscription *sub);
@@ -126,6 +129,7 @@ private:
QByteArray writePublishProperties(const QMqttPublishProperties &properties);
QByteArray writeSubscriptionProperties(const QMqttSubscriptionProperties &properties);
QByteArray writeUnsubscriptionProperties(const QMqttUnsubscriptionProperties &properties);
+ QByteArray writeAuthenticationProperties(const QMqttAuthenticationProperties &properties);
void closeConnection(QMqttClient::ClientError error);
QByteArray readBuffer(quint64 size);
template<typename T> T readBufferTyped(qint64 *dataSize = nullptr);
diff --git a/src/mqtt/qmqttcontrolpacket_p.h b/src/mqtt/qmqttcontrolpacket_p.h
index a5f17e7..740b1c4 100644
--- a/src/mqtt/qmqttcontrolpacket_p.h
+++ b/src/mqtt/qmqttcontrolpacket_p.h
@@ -66,6 +66,7 @@ public:
PINGREQ = 0xC0,
PINGRESP = 0xD0,
DISCONNECT = 0xE0,
+ AUTH = 0xF0,
};
QMqttControlPacket();
diff --git a/tests/auto/qmqttclient/tst_qmqttclient.cpp b/tests/auto/qmqttclient/tst_qmqttclient.cpp
index 6d81e27..f2e0888 100644
--- a/tests/auto/qmqttclient/tst_qmqttclient.cpp
+++ b/tests/auto/qmqttclient/tst_qmqttclient.cpp
@@ -68,6 +68,7 @@ private Q_SLOTS:
void openIODevice_QTBUG66955();
void staticProperties_QTBUG_67176_data();
void staticProperties_QTBUG_67176();
+ void authentication();
private:
QProcess m_brokerProcess;
QString m_testBroker;
@@ -595,6 +596,35 @@ void Tst_QMqttClient::staticProperties_QTBUG_67176()
QCOMPARE(client.cleanSession(), clean);
}
+void Tst_QMqttClient::authentication()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ QMqttConnectionProperties connectionProperties;
+ connectionProperties.setAuthenticationMethod(QLatin1String("SCRAM-SHA-1"));
+ client.setConnectionProperties(connectionProperties);
+
+ connect(&client, &QMqttClient::authenticationRequested, [](const QMqttAuthenticationProperties &prop)
+ {
+ qDebug() << "Authentication requested:" << prop.authenticationMethod();
+ });
+
+ connect(&client, &QMqttClient::authenticationFinished, [](const QMqttAuthenticationProperties &prop)
+ {
+ qDebug() << "Authentication finished:" << prop.authenticationMethod();
+ });
+
+ // ### FIXME : There is no public test broker yet able to handle authentication methods
+ // Theoretically the broker should send an AUTH request, followed by AUTH call including
+ // authentication data. See 4.12 of MQTT v5 specs.
+ QSKIP("No broker available with enhanced authentication.");
+ client.connectToHost();
+ QTRY_COMPARE(client.state(), QMqttClient::Connected);
+}
+
QTEST_MAIN(Tst_QMqttClient)
#include "tst_qmqttclient.moc"