diff --git a/data/dbus-1/io.guh.nymead.conf b/data/dbus-1/io.guh.nymead.conf
new file mode 100644
index 00000000..4106861d
--- /dev/null
+++ b/data/dbus-1/io.guh.nymead.conf
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/debian/control b/debian/control
index ab3bebec..a3203962 100644
--- a/debian/control
+++ b/debian/control
@@ -25,6 +25,7 @@ Build-Depends: debhelper (>= 9.0.0),
libssl-dev,
libmbedtls-dev,
libaws-iot-device-sdk-cpp,
+ dbus-test-runner,
Package: guh
Architecture: any
diff --git a/debian/guhd.install.in b/debian/guhd.install.in
index 80a3b6c1..d6e71ad5 100644
--- a/debian/guhd.install.in
+++ b/debian/guhd.install.in
@@ -5,3 +5,4 @@ usr/lib/@DEB_HOST_MULTIARCH@/libguh-core.so.1.0
usr/lib/@DEB_HOST_MULTIARCH@/libguh-core.so.1.0.0
data/systemd/guhd.service /lib/systemd/system/
data/logrotate/guhd /etc/logrotate.d/
+data/dbus-1/io.guh.nymead.conf /etc/dbus-1/system.d/
diff --git a/debian/rules b/debian/rules
index 7bc240e6..19de33ef 100755
--- a/debian/rules
+++ b/debian/rules
@@ -43,7 +43,7 @@ override_dh_install: $(PREPROCESS_FILES:.in=)
cp -a $(CURDIR)/doc/html $(CURDIR)/debian/guh-doc/usr/share/doc/guh/ || true
override_dh_auto_test:
- dh_auto_test -- -k TESTARGS="-o -,txt -o test-results.xml,xunitxml"
+ dh_auto_test -- -k TESTARGS="-m 120 -p -o -p -,txt -p -o -p test-results.xml,xunitxml" TESTRUNNER="dbus-test-runner --bus-type=system --task"
override_dh_strip:
dh_strip --dbg-package=guh-dbg
diff --git a/guh.pri b/guh.pri
index dfbcd205..06b0afb0 100644
--- a/guh.pri
+++ b/guh.pri
@@ -6,7 +6,7 @@ GUH_PLUGINS_PATH=/usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')/gu
# define protocol versions
JSON_PROTOCOL_VERSION_MAJOR=0
-JSON_PROTOCOL_VERSION_MINOR=53
+JSON_PROTOCOL_VERSION_MINOR=54
REST_API_VERSION=1
DEFINES += GUH_VERSION_STRING=\\\"$${GUH_VERSION_STRING}\\\" \
diff --git a/libguh-core/guhcore.cpp b/libguh-core/guhcore.cpp
index 32ad385d..94d5ee0d 100644
--- a/libguh-core/guhcore.cpp
+++ b/libguh-core/guhcore.cpp
@@ -438,6 +438,9 @@ void GuhCore::init() {
qCDebug(dcApplication) << "Creating Rule Engine";
m_ruleEngine = new RuleEngine(this);
+ qCDebug(dcApplication()) << "Creating User Manager";
+ m_userManager = new UserManager(this);
+
qCDebug(dcApplication) << "Creating Server Manager";
m_serverManager = new ServerManager(m_configuration, this);
@@ -445,9 +448,6 @@ void GuhCore::init() {
qCDebug(dcApplication) << "Creating Network Manager";
m_networkManager = new NetworkManager(this);
- qCDebug(dcApplication) << "Creating User Manager";
- m_userManager = new UserManager(this);
-
qCDebug(dcApplication) << "Creating Cloud Manager";
m_cloudManager = new CloudManager(m_networkManager, this);
m_cloudManager->setDeviceId(m_configuration->serverUuid());
diff --git a/libguh-core/guhdbusservice.cpp b/libguh-core/guhdbusservice.cpp
new file mode 100644
index 00000000..969b24ae
--- /dev/null
+++ b/libguh-core/guhdbusservice.cpp
@@ -0,0 +1,61 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "guhdbusservice.h"
+
+namespace guhserver {
+
+QDBusConnection GuhDBusService::s_connection = QDBusConnection::systemBus();
+
+GuhDBusService::GuhDBusService(const QString &objectPath, UserManager *parent) : QObject(parent)
+{
+ bool status = s_connection.registerService("io.guh.nymead");
+ if (!status) {
+ qCWarning(dcApplication) << "Failed to register D-Bus service.";
+ return;
+ }
+ status = s_connection.registerObject(objectPath, this, QDBusConnection::ExportScriptableContents);
+ if (!status) {
+ qCWarning(dcApplication()) << "Failed to register D-Bus object.";
+ return;
+ }
+ m_isValid = true;
+}
+
+bool GuhDBusService::isValid()
+{
+ return m_isValid;
+}
+
+QDBusConnection GuhDBusService::connection() const
+{
+ return s_connection;
+}
+
+void GuhDBusService::setBusType(QDBusConnection::BusType busType)
+{
+ if (busType == QDBusConnection::SessionBus) {
+ s_connection = QDBusConnection::sessionBus();
+ } else {
+ s_connection = QDBusConnection::systemBus();
+ }
+}
+
+}
diff --git a/libguh-core/guhdbusservice.h b/libguh-core/guhdbusservice.h
new file mode 100644
index 00000000..e5717050
--- /dev/null
+++ b/libguh-core/guhdbusservice.h
@@ -0,0 +1,53 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef GUHDBUSSERVICE_H
+#define GUHDBUSSERVICE_H
+
+#include
+#include
+#include "guhcore.h"
+
+namespace guhserver {
+
+class GuhDBusService : public QObject, public QDBusContext
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "io.guh.nymead")
+
+public:
+ explicit GuhDBusService(const QString &objectPath, UserManager *parent = nullptr);
+
+ static void setBusType(QDBusConnection::BusType busType);
+
+ bool isValid();
+
+protected:
+ QDBusConnection connection() const;
+
+private:
+ static QDBusConnection s_connection;
+ bool m_isValid = false;
+
+};
+
+}
+
+#endif // GUHDBUSSERVICE_H
diff --git a/libguh-core/jsonrpc/jsonhandler.cpp b/libguh-core/jsonrpc/jsonhandler.cpp
index 9bb16296..736b986e 100644
--- a/libguh-core/jsonrpc/jsonhandler.cpp
+++ b/libguh-core/jsonrpc/jsonhandler.cpp
@@ -328,7 +328,7 @@ void JsonReply::setCommandId(int commandId)
/*! Start the timeout timer for this \l{JsonReply}. The default timeout is 15 seconds. */
void JsonReply::startWait()
{
- m_timeout.start(15000);
+ m_timeout.start(30000);
}
void JsonReply::timeout()
diff --git a/libguh-core/jsonrpc/jsonrpcserver.cpp b/libguh-core/jsonrpc/jsonrpcserver.cpp
index 24814487..5cea0ec3 100644
--- a/libguh-core/jsonrpc/jsonrpcserver.cpp
+++ b/libguh-core/jsonrpc/jsonrpcserver.cpp
@@ -86,6 +86,7 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
returns.insert("protocol version", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("initialSetupRequired", JsonTypes::basicTypeToString(JsonTypes::Bool));
returns.insert("authenticationRequired", JsonTypes::basicTypeToString(JsonTypes::Bool));
+ returns.insert("pushButtonAuthAvailable", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("Hello", returns);
params.clear(); returns.clear();
@@ -118,17 +119,40 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
setReturns("CreateUser", returns);
params.clear(); returns.clear();
- setDescription("Authenticate", "Authenticate a client to the api. This will return a new token to be used to authorize a client at the API.");
+ setDescription("Authenticate", "Authenticate a client to the api via user & password challenge. Provide "
+ "a device name which allows the user to identify the client and revoke the token in case "
+ "the device is lost or stolen. This will return a new token to be used to authorize a "
+ "client at the API.");
params.insert("username", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("password", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("deviceName", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("Authenticate", params);
- returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::String));
+ returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::Bool));
returns.insert("o:token", JsonTypes::basicTypeToString(JsonTypes::String));
setReturns("Authenticate", returns);
params.clear(); returns.clear();
- setDescription("Tokens", "Return a list of if TokenInfo objects all the tokens for the current user.");
+ setDescription("RequestPushButtonAuth", "Authenticate a client to the api via Push Button method. "
+ "Provide a device name which allows the user to identify the client and revoke the "
+ "token in case the device is lost or stolen. If push button hardware is available, "
+ "this will return with success and start listening for push button presses. When the "
+ "push button is pressed, the PushButtonAuthFinished notification will be sent to the "
+ "requesting client. The procedure will be cancelled when the connection is interrupted. "
+ "If another client requests push button authentication while a procedure is still going "
+ "on, the second call will take over and the first one will be notified by the "
+ "PushButtonAuthFinished signal about the error. The application should make it clear "
+ "to the user to not press the button when the procedure fails as this can happen for 2 "
+ "reasons: a) a second user is trying to auth at the same time and only the currently "
+ "active user should press the button or b) it might indicate an attacker trying to take "
+ "over and snooping in for tokens.");
+ params.insert("deviceName", JsonTypes::basicTypeToString(JsonTypes::String));
+ setParams("RequestPushButtonAuth", params);
+ returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::Bool));
+ returns.insert("transactionId", JsonTypes::basicTypeToString(JsonTypes::Int));
+ setReturns("RequestPushButtonAuth", returns);
+
+ params.clear(); returns.clear();
+ setDescription("Tokens", "Return a list of TokenInfo objects of all the tokens for the current user.");
setParams("Tokens", params);
returns.insert("tokenInfoList", QVariantList() << JsonTypes::tokenInfoRef());
setReturns("Tokens", returns);
@@ -168,7 +192,16 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
params.insert("connected", JsonTypes::basicTypeToString(JsonTypes::Bool));
setParams("CloudConnectedChanged", params);
+ params.clear();
+ setDescription("PushButtonAuthFinished", "Emitted when a push button authentication reaches final state. NOTE: This notification is special. It will only be emitted to connections that did actively request a push button authentication, but also it will be emitted regardless of the notification settings. ");
+ params.insert("status", JsonTypes::userErrorRef());
+ params.insert("transactionId", JsonTypes::basicTypeToString(JsonTypes::Int));
+ params.insert("o:token", JsonTypes::basicTypeToString(JsonTypes::String));
+ setParams("PushButtonAuthFinished", params);
+
QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection);
+
+ connect(GuhCore::instance()->userManager(), &UserManager::pushButtonAuthFinished, this, &JsonRPCServer::onPushButtonAuthFinished);
}
/*! Returns the \e namespace of \l{JsonHandler}. */
@@ -218,9 +251,9 @@ JsonReply* JsonRPCServer::Version(const QVariantMap ¶ms) const
JsonReply* JsonRPCServer::SetNotificationStatus(const QVariantMap ¶ms)
{
QUuid clientId = this->property("clientId").toUuid();
- m_clients[clientId] = params.value("enabled").toBool();
+ m_clientNotifications[clientId] = params.value("enabled").toBool();
QVariantMap returns;
- returns.insert("enabled", m_clients[clientId]);
+ returns.insert("enabled", m_clientNotifications[clientId]);
return createReply(returns);
}
@@ -251,6 +284,21 @@ JsonReply *JsonRPCServer::Authenticate(const QVariantMap ¶ms)
return createReply(ret);
}
+JsonReply *JsonRPCServer::RequestPushButtonAuth(const QVariantMap ¶ms)
+{
+ QString deviceName = params.value("deviceName").toString();
+ QUuid clientId = this->property("clientId").toUuid();
+
+ int transactionId = GuhCore::instance()->userManager()->requestPushButtonAuth(deviceName);
+ m_pushButtonTransactions.insert(transactionId, clientId);
+
+ QVariantMap data;
+ data.insert("transactionId", transactionId);
+ // TODO: return false if pushbutton auth is disabled in settings
+ data.insert("success", true);
+ return createReply(data);
+}
+
JsonReply *JsonRPCServer::Tokens(const QVariantMap ¶ms) const
{
Q_UNUSED(params)
@@ -390,6 +438,7 @@ QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface) c
handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? GuhCore::instance()->userManager()->users().isEmpty() : false));
handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled);
+ handshake.insert("pushButtonAuthAvailable", GuhCore::instance()->userManager()->pushButtonAuthAvailable());
return handshake;
}
@@ -444,16 +493,18 @@ void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &data)
// check if authentication is required for this transport
if (m_interfaces.value(interface)) {
- // if there is no user in the system yet, let's fail unless this is a CreateUser, Introspect or Hello call
+ QByteArray token = message.value("token").toByteArray();
+ QStringList authExemptMethodsNoUser = {"Introspect", "Hello", "CreateUser", "RequestPushButtonAuth"};
+ QStringList authExemptMethodsWithUser = {"Introspect", "Hello", "Authenticate", "RequestPushButtonAuth"};
+ // if there is no user in the system yet, let's fail unless this is special method for authentication itself
if (GuhCore::instance()->userManager()->users().isEmpty()) {
- if (!(targetNamespace == "JSONRPC" && (method == "CreateUser" || method == "Introspect" || method == "Hello"))) {
+ if (!(targetNamespace == "JSONRPC" && authExemptMethodsNoUser.contains(method)) && (token.isEmpty() || !GuhCore::instance()->userManager()->verifyToken(token))) {
sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call CreateUser first.");
return;
}
} else {
- // ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate, Introspect or Hello call
- QByteArray token = message.value("token").toByteArray();
- if (!(targetNamespace == "JSONRPC" && (method == "Authenticate" || method == "Introspect" || method == "Hello")) && !GuhCore::instance()->userManager()->verifyToken(token)) {
+ // ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate, Introspect Hello call
+ if (!(targetNamespace == "JSONRPC" && authExemptMethodsWithUser.contains(method)) && (token.isEmpty() || !GuhCore::instance()->userManager()->verifyToken(token))) {
sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token.");
return;
}
@@ -522,8 +573,8 @@ void JsonRPCServer::sendNotification(const QVariantMap ¶ms)
notification.insert("notification", handler->name() + "." + method.name());
notification.insert("params", params);
- foreach (TransportInterface *interface, m_interfaces.keys()) {
- interface->sendData(m_clients.keys(true), QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact));
+ foreach (const QUuid &clientId, m_clientNotifications.keys(true)) {
+ m_clientTransports.value(clientId)->sendData(clientId, QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact));
}
}
@@ -567,6 +618,35 @@ void JsonRPCServer::onCloudConnectedChanged(bool connected)
emit CloudConnectedChanged(params);
}
+void JsonRPCServer::onPushButtonAuthFinished(int transactionId, bool success, const QByteArray &token)
+{
+ QUuid clientId = m_pushButtonTransactions.take(transactionId);
+ if (clientId.isNull()) {
+ qCDebug(dcJsonRpc()) << "Received a PushButton reply but wasn't expecting it.";
+ return;
+ }
+
+ TransportInterface *transport = m_clientTransports.value(clientId);
+ if (!transport) {
+ qCWarning(dcJsonRpc()) << "No transport for given clientId";
+ return;
+ }
+
+ QVariantMap params;
+ params.insert("transactionId", transactionId);
+ params.insert("success", success);
+ if (success) {
+ params.insert("token", token);
+ }
+
+ QVariantMap notification;
+ notification.insert("id", transactionId);
+ notification.insert("notification", "JSONRPC.PushButtonAuthFinished");
+ notification.insert("params", params);
+
+ transport->sendData(clientId, QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact));
+}
+
void JsonRPCServer::registerHandler(JsonHandler *handler)
{
m_handlers.insert(handler->name(), handler);
@@ -582,15 +662,21 @@ void JsonRPCServer::clientConnected(const QUuid &clientId)
{
TransportInterface *interface = qobject_cast(sender());
+ m_clientTransports.insert(clientId, interface);
+
// If authentication is required, notifications are disabled by default. Clients must enable them with a valid token
- m_clients.insert(clientId, !interface->configuration().authenticationEnabled);
+ m_clientNotifications.insert(clientId, !interface->configuration().authenticationEnabled);
interface->sendData(clientId, QJsonDocument::fromVariant(createWelcomeMessage(interface)).toJson(QJsonDocument::Compact));
}
void JsonRPCServer::clientDisconnected(const QUuid &clientId)
{
- m_clients.remove(clientId);
+ m_clientTransports.remove(clientId);
+ m_clientNotifications.remove(clientId);
+ if (m_pushButtonTransactions.values().contains(clientId)) {
+ GuhCore::instance()->userManager()->cancelPushButtonAuth(m_pushButtonTransactions.key(clientId));
+ }
}
}
diff --git a/libguh-core/jsonrpc/jsonrpcserver.h b/libguh-core/jsonrpc/jsonrpcserver.h
index 52ae53ad..25a00908 100644
--- a/libguh-core/jsonrpc/jsonrpcserver.h
+++ b/libguh-core/jsonrpc/jsonrpcserver.h
@@ -54,6 +54,7 @@ public:
Q_INVOKABLE JsonReply *CreateUser(const QVariantMap ¶ms);
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap ¶ms);
+ Q_INVOKABLE JsonReply *RequestPushButtonAuth(const QVariantMap ¶ms);
Q_INVOKABLE JsonReply *Tokens(const QVariantMap ¶ms) const;
Q_INVOKABLE JsonReply *RemoveToken(const QVariantMap ¶ms);
Q_INVOKABLE JsonReply *SetupRemoteAccess(const QVariantMap ¶ms);
@@ -62,8 +63,9 @@ public:
signals:
void CloudConnectedChanged(const QVariantMap &map);
+ void PushButtonAuthFinished(const QVariantMap ¶ms);
- // Internal
+ // Server API
public:
void registerTransportInterface(TransportInterface *interface, bool authenticationRequired);
void unregisterTransportInterface(TransportInterface *interface);
@@ -90,14 +92,16 @@ private slots:
void pairingFinished(QString cognitoUserId, int status, const QString &message);
void onCloudConnectedChanged(bool connected);
+ void onPushButtonAuthFinished(int transactionId, bool success, const QByteArray &token);
private:
- QMap m_interfaces;
+ QMap m_interfaces; // Interface, authenticationRequired
QHash m_handlers;
QHash m_asyncReplies;
- // clientId, notificationsEnabled
- QHash m_clients;
+ QHash m_clientTransports;
+ QHash m_clientNotifications;
+ QHash m_pushButtonTransactions;
QHash m_pairingRequests;
diff --git a/libguh-core/libguh-core.pro b/libguh-core/libguh-core.pro
index ac432317..a9d2371d 100644
--- a/libguh-core/libguh-core.pro
+++ b/libguh-core/libguh-core.pro
@@ -75,6 +75,9 @@ HEADERS += guhcore.h \
cloudmanager.h \
MbedTLS/MbedTLSConnection.hpp \
janusconnector.h \
+ pushbuttondbusservice.h \
+ guhdbusservice.h
+
SOURCES += guhcore.cpp \
tcpserver.cpp \
@@ -132,3 +135,5 @@ SOURCES += guhcore.cpp \
cloudmanager.cpp \
MbedTLS/MbedTLSConnection.cpp \
janusconnector.cpp \
+ pushbuttondbusservice.cpp \
+ guhdbusservice.cpp
diff --git a/libguh-core/pushbuttondbusservice.cpp b/libguh-core/pushbuttondbusservice.cpp
new file mode 100644
index 00000000..0214710d
--- /dev/null
+++ b/libguh-core/pushbuttondbusservice.cpp
@@ -0,0 +1,87 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "pushbuttondbusservice.h"
+#include "loggingcategories.h"
+
+#include
+#include
+#include
+
+namespace guhserver {
+
+PushButtonDBusService::PushButtonDBusService(const QString &objectPath, UserManager *parent) : GuhDBusService(objectPath, parent),
+ m_userManager(parent)
+{
+ if (!isValid()) {
+ qCWarning(dcUserManager()) << "Failed to register PushButton D-Bus service.";
+ return;
+ }
+ qCDebug(dcUserManager()) << "PushButton D-Bus service set up.";
+}
+
+bool PushButtonDBusService::agentAvailable() const
+{
+ return m_registeredAgents.count() > 0;
+}
+
+void PushButtonDBusService::RegisterButtonAgent(const QDBusObjectPath &agentPath)
+{
+ QDBusMessage msg = message();
+ connection().connect(QString(), QString(agentPath.path()), QString(), "PushButtonPressed", this, SIGNAL(pushButtonPressed()));
+ QDBusServiceWatcher *watcher = new QDBusServiceWatcher(msg.service(), connection(), QDBusServiceWatcher::WatchForUnregistration, this);
+ connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &PushButtonDBusService::serviceUnregistered);
+ qCDebug(dcUserManager()) << "PushButton handler" << agentPath.path() << msg.service() << "registered.";
+ m_registeredAgents.append(msg.service());
+}
+
+void PushButtonDBusService::UnregisterButtonAgent()
+{
+ QDBusMessage msg = message();
+ serviceUnregistered(msg.service());
+}
+
+QByteArray PushButtonDBusService::GenerateAuthToken(const QString &deviceName)
+{
+ int transactionId = m_userManager->requestPushButtonAuth(deviceName);
+ bool success = false;
+ QByteArray token;
+ QMetaObject::Connection c = connect(m_userManager, &UserManager::pushButtonAuthFinished, this, [&] (int i, bool s, const QByteArray &t) {
+ if (transactionId == i) {
+ success = s;
+ token = t;
+ }
+ });
+ emit pushButtonPressed();
+ disconnect(c);
+ return token;
+}
+
+void PushButtonDBusService::serviceUnregistered(const QString &serviceName)
+{
+ if (!m_registeredAgents.contains(serviceName)) {
+ qCWarning(dcUserManager()) << "PushButton agent" << serviceName << "not known. Cannot unregister.";
+ return;
+ }
+ m_registeredAgents.removeAll(serviceName);
+ qCDebug(dcUserManager()) << "PushButton agent" << serviceName << "unregistered";
+}
+
+}
diff --git a/libguh-core/pushbuttondbusservice.h b/libguh-core/pushbuttondbusservice.h
new file mode 100644
index 00000000..40d83e0a
--- /dev/null
+++ b/libguh-core/pushbuttondbusservice.h
@@ -0,0 +1,56 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef PUSHBUTTONDBUSSERVICE_H
+#define PUSHBUTTONDBUSSERVICE_H
+
+#include
+#include "usermanager.h"
+#include "guhdbusservice.h"
+
+namespace guhserver {
+
+class PushButtonDBusService : public GuhDBusService
+{
+ Q_OBJECT
+public:
+ explicit PushButtonDBusService(const QString &objectPath, UserManager *parent);
+
+ bool agentAvailable() const;
+
+ Q_SCRIPTABLE void RegisterButtonAgent(const QDBusObjectPath &agentPath);
+ Q_SCRIPTABLE void UnregisterButtonAgent();
+
+ Q_SCRIPTABLE QByteArray GenerateAuthToken(const QString &deviceName);
+
+signals:
+ void pushButtonPressed();
+
+private slots:
+ void serviceUnregistered(const QString &serviceName);
+
+private:
+ UserManager *m_userManager;
+ QStringList m_registeredAgents;
+};
+
+}
+
+#endif // PUSHBUTTONDBUSSERVICE_H
diff --git a/libguh-core/tcpserver.cpp b/libguh-core/tcpserver.cpp
index 35c70475..2c90b768 100644
--- a/libguh-core/tcpserver.cpp
+++ b/libguh-core/tcpserver.cpp
@@ -81,6 +81,8 @@ void TcpServer::sendData(const QUuid &clientId, const QByteArray &data)
client = m_clientList.value(clientId);
if (client) {
client->write(data + '\n');
+ } else {
+ qWarning(dcTcpServer()) << "Client" << clientId << "unknown to this transport";
}
}
diff --git a/libguh-core/usermanager.cpp b/libguh-core/usermanager.cpp
index 5b1932fd..3de327fb 100644
--- a/libguh-core/usermanager.cpp
+++ b/libguh-core/usermanager.cpp
@@ -22,6 +22,7 @@
#include "guhsettings.h"
#include "loggingcategories.h"
#include "guhcore.h"
+#include "pushbuttondbusservice.h"
#include
#include
@@ -44,6 +45,10 @@ UserManager::UserManager(QObject *parent) : QObject(parent)
return;
}
initDB();
+
+ m_pushButtonDBusService = new PushButtonDBusService("/io/guh/nymead/UserManager", this);
+ connect(m_pushButtonDBusService, &PushButtonDBusService::pushButtonPressed, this, &UserManager::onPushButtonPressed);
+ m_pushButtonTransaction = qMakePair(-1, QString());
}
QStringList UserManager::users() const
@@ -105,6 +110,11 @@ UserManager::UserError UserManager::removeUser(const QString &username)
return UserErrorNoError;
}
+bool UserManager::pushButtonAuthAvailable() const
+{
+ return m_pushButtonDBusService->agentAvailable();
+}
+
QByteArray UserManager::authenticate(const QString &username, const QString &password, const QString &deviceName)
{
if (!validateUsername(username)) {
@@ -142,6 +152,35 @@ QByteArray UserManager::authenticate(const QString &username, const QString &pas
return token;
}
+int UserManager::requestPushButtonAuth(const QString &deviceName)
+{
+ if (m_pushButtonTransaction.first != -1) {
+ qCWarning(dcUserManager()) << "PushButton authentication already in progress for device" << m_pushButtonTransaction.second << ". Cancelling...";
+ cancelPushButtonAuth(m_pushButtonTransaction.first);
+ }
+
+ qCDebug(dcUserManager()) << "Starting PushButton authentication for device" << deviceName;
+ int transactionId = ++m_pushButtonTransactionIdCounter;
+ m_pushButtonTransaction = qMakePair(transactionId, deviceName);
+ return transactionId;
+}
+
+void UserManager::cancelPushButtonAuth(int transactionId)
+{
+ if (m_pushButtonTransaction.first == -1) {
+ qCWarning(dcUserManager()) << "No PushButton transaction in progress. Nothing to cancel.";
+ return;
+ }
+ if (m_pushButtonTransaction.first != transactionId) {
+ qCWarning(dcUserManager()) << "PushButton transaction" << transactionId << "not in progress. Cannot cancel.";
+ return;
+ }
+ qCDebug(dcUserManager()) << "Cancelling PushButton transaction for device:" << m_pushButtonTransaction.second;
+ emit pushButtonAuthFinished(m_pushButtonTransaction.first, false, QByteArray());
+ m_pushButtonTransaction.first = -1;
+
+}
+
QString UserManager::userForToken(const QByteArray &token) const
{
if (!validateToken(token)) {
@@ -220,7 +259,7 @@ bool UserManager::verifyToken(const QByteArray &token)
qCDebug(dcUserManager) << "Authorisation failed for token" << token;
return false;
}
- qCDebug(dcUserManager) << "Token authorized for user" << result.value("username").toString();
+ //qCDebug(dcUserManager) << "Token authorized for user" << result.value("username").toString();
return true;
}
@@ -246,4 +285,30 @@ bool UserManager::validateToken(const QByteArray &token) const
return validator.exactMatch(token);
}
+void UserManager::onPushButtonPressed()
+{
+ if (m_pushButtonTransaction.first == -1) {
+ qCDebug(dcUserManager()) << "PushButton pressed but don't have a transaction waiting for it.";
+ return;
+ }
+
+ QByteArray token = QCryptographicHash::hash(QUuid::createUuid().toByteArray(), QCryptographicHash::Sha256).toBase64();
+ QString storeTokenQuery = QString("INSERT INTO tokens(id, username, token, creationdate, devicename) VALUES(\"%1\", \"%2\", \"%3\", \"%4\", \"%5\");")
+ .arg(QUuid::createUuid().toString())
+ .arg("")
+ .arg(QString::fromUtf8(token))
+ .arg(GuhCore::instance()->timeManager()->currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
+ .arg(m_pushButtonTransaction.second);
+
+ m_db.exec(storeTokenQuery);
+ if (m_db.lastError().type() != QSqlError::NoError) {
+ qCWarning(dcUserManager) << "Error storing token in DB:" << m_db.lastError().databaseText() << m_db.lastError().driverText();
+ emit pushButtonAuthFinished(m_pushButtonTransaction.first, false, QByteArray());
+ }
+ qCDebug(dcUserManager()) << "PushButton Auth succeeded";
+ emit pushButtonAuthFinished(m_pushButtonTransaction.first, true, token);
+
+ m_pushButtonTransaction.first = -1;
+}
+
}
diff --git a/libguh-core/usermanager.h b/libguh-core/usermanager.h
index a282b0d9..01a9f452 100644
--- a/libguh-core/usermanager.h
+++ b/libguh-core/usermanager.h
@@ -28,6 +28,8 @@
namespace guhserver {
+class PushButtonDBusService;
+
class UserManager : public QObject
{
Q_OBJECT
@@ -50,20 +52,33 @@ public:
UserError createUser(const QString &username, const QString &password);
UserError removeUser(const QString &username);
+ bool pushButtonAuthAvailable() const;
+
QByteArray authenticate(const QString &username, const QString &password, const QString &deviceName);
+ int requestPushButtonAuth(const QString &deviceName);
+ void cancelPushButtonAuth(int transactionId);
QString userForToken(const QByteArray &token) const;
QList tokens(const QString &username) const;
guhserver::UserManager::UserError removeToken(const QUuid &tokenId);
bool verifyToken(const QByteArray &token);
+signals:
+ void pushButtonAuthFinished(int transactionId, bool success, const QByteArray &token);
+
private:
void initDB();
bool validateUsername(const QString &username) const;
bool validateToken(const QByteArray &token) const;
+private slots:
+ void onPushButtonPressed();
+
private:
QSqlDatabase m_db;
+ PushButtonDBusService *m_pushButtonDBusService = nullptr;
+ int m_pushButtonTransactionIdCounter = 0;
+ QPair m_pushButtonTransaction;
};
diff --git a/libguh-core/websocketserver.cpp b/libguh-core/websocketserver.cpp
index 0be0e6a4..cdf5c4ce 100644
--- a/libguh-core/websocketserver.cpp
+++ b/libguh-core/websocketserver.cpp
@@ -93,6 +93,8 @@ void WebSocketServer::sendData(const QUuid &clientId, const QByteArray &data)
if (client) {
qCDebug(dcWebSocketServerTraffic()) << "Sending data to client" << data;
client->sendTextMessage(data + '\n');
+ } else {
+ qCWarning(dcWebSocketServer()) << "Client" << clientId << "unknown to this transport";
}
}
diff --git a/plugins/mock/translations/de_DE.ts b/plugins/mock/translations/de_DE.ts
index ed8f7955..19d60bc3 100644
--- a/plugins/mock/translations/de_DE.ts
+++ b/plugins/mock/translations/de_DE.ts
@@ -12,230 +12,230 @@
MockDevice
-
+ guhThe name of the vendor (2062d64d-3232-433c-88bc-0d33c0ba2ba6)guh
-
+ Mock DeviceThe name of the DeviceClass (753f0d32-0468-4d08-82ed-1964aab03298)Mock Gerät
-
+ http portThe name of the paramType (d4f06047-125e-4479-9810-b54c189917f5) of Mock DeviceHTTP Port
-
+ Mock Action 3 (async)The name of the ActionType fbae06d3-7666-483e-a39e-ec50fe89054e of deviceClass Mock DeviceMock Aktion 3 (async)
-
+ Mock Action 4 (broken)The name of the ActionType df3cf33d-26d5-4577-9132-9823bd33fad0 of deviceClass Mock DeviceMock Aktion 4 (kaputt)
-
+ Mock Action 5 (async, broken)The name of the ActionType bfe89a1d-3497-4121-8318-e77c37537219 of deviceClass Mock DeviceMock Aktion 5 (async, kaputt)
-
+ Mock Device (Auto created)The name of the DeviceClass (ab4257b3-7548-47ee-9bd4-7dc3004fd197)Mock Gerät (Auto erstellt)
-
+ Mock Device (Push Button)The name of the DeviceClass (9e03144c-e436-4eea-82d9-ccb33ef778db)Mock Gerät (Drückknopf)
-
+ Wait 3 second before you continue, the push button will be pressed automatically.The pairing info of deviceClass Mock Device (Push Button)Warte 3 Sekunden bevor du fortfährst, the Knopf wird automatisch gerückt.
-
+ configParamIntThe name of the paramType (e1f72121-a426-45e2-b475-8262b5cdf103) of Mock DevicesconfigParamInt
-
+ configParamBoolThe name of the paramType (c75723b6-ea4f-4982-9751-6c5e39c88145) of Mock DevicesconfigParamBool
-
+ asyncThe name of the paramType (f2977061-4dd0-4ef5-85aa-3b7134743be3) of Mock Deviceasync
-
+ brokenThe name of the paramType (ae8f8901-f2c1-42a5-8111-6d2fc8e4c1e4) of Mock Devicekaputt
-
+ resultCountThe name of the paramType (d222adb4-2f9c-4c3f-8655-76400d0fb6ce) of Mock DeviceResultat Anzahl
-
+ Dummy int state changedThe name of the autocreated EventType (80baec19-54de-4948-ac46-31eabfaceb83)Int Zustand verändert
-
+ Dummy int stateThe name of the ParamType of StateType (80baec19-54de-4948-ac46-31eabfaceb83) of DeviceClass Mock DeviceDummy Int Zustand
-
+ Dummy bool state changedThe name of the autocreated EventType (9dd6a97c-dfd1-43dc-acbd-367932742310)Bool Zustand verändert
-
+ Dummy bool stateThe name of the ParamType of StateType (9dd6a97c-dfd1-43dc-acbd-367932742310) of DeviceClass Mock DeviceDummy Bool Zustand
-
+ Mock Action 1 (with params)The name of the ActionType dea0f4e1-65e3-4981-8eaa-2701c53a9185 of deviceClass Mock DeviceMock Aktion 1 (Mit Parametern)
-
+ mockActionParam1The name of the paramType (a2d3a256-a551-4712-a65b-ecd5a436a1cb) of Mock DevicemockActionParam1
-
+ mockActionParam2The name of the paramType (304a4899-18be-4e3b-94f4-d03be52f3233) of Mock DevicemockActionParam2
-
+ Mock Action 2 (without params)The name of the ActionType defd3ed6-1a0d-400b-8879-a0202cf39935 of deviceClass Mock DeviceMock Aktion (ohne Parameter
-
+ mockParamIntThe name of the paramType (0550e16d-60b9-4ba5-83f4-4d3cee656121) of Mock DevicemockParamInt
-
+ Mock Event 1The name of the EventType 45bf3752-0fc6-46b9-89fd-ffd878b5b22b of deviceClass Mock Device (Auto created)Mock Event 1
-
+ Mock Event 2The name of the EventType 863d5920-b1cf-4eb9-88bd-8f7b8583b1cf of deviceClass Mock Device (Auto created)Mock Event 2
-
+ color changedThe name of the autocreated EventType (20dc7c22-c50e-42db-837c-2bbced939f8e)Farbe geändert
-
+ colorThe name of the ParamType of StateType (20dc7c22-c50e-42db-837c-2bbced939f8e) of DeviceClass Mock Device (Push Button)Farbe
-
+ Set colorThe name of the autocreated ActionType (20dc7c22-c50e-42db-837c-2bbced939f8e)Setze Farbe
-
+ percentage changedThe name of the autocreated EventType (72981c04-267a-4ba0-a59e-9921d2f3af9c)Prozent gändert
-
+ percentageThe name of the ParamType of StateType (72981c04-267a-4ba0-a59e-9921d2f3af9c) of DeviceClass Mock Device (Push Button)Prozent
-
+ Set percentageThe name of the autocreated ActionType (72981c04-267a-4ba0-a59e-9921d2f3af9c)Setze Prozentwert
-
+ allowed values changedThe name of the autocreated EventType (05f63f9c-f61e-4dcf-ad55-3f13fde2765b)Erlaubter Wert geändert
-
+ allowed valuesThe name of the ParamType of StateType (05f63f9c-f61e-4dcf-ad55-3f13fde2765b) of DeviceClass Mock Device (Push Button)Erlaubte Werte
-
+ Set allowed valuesThe name of the autocreated ActionType (05f63f9c-f61e-4dcf-ad55-3f13fde2765b)Setze erlaubten Wert
-
+ double value changedThe name of the autocreated EventType (53cd7c55-49b7-441b-b970-9048f20f0e2c)Double Wert geändert
-
+ double valueThe name of the ParamType of StateType (53cd7c55-49b7-441b-b970-9048f20f0e2c) of DeviceClass Mock Device (Push Button)Double Wert
-
+ Set double valueThe name of the autocreated ActionType (53cd7c55-49b7-441b-b970-9048f20f0e2c)Setze double Wert
-
-
+
+ Set bool valueThe name of the autocreated ActionType (e680f7a4-b39e-46da-be41-fa3170fe3768)
----------
@@ -243,20 +243,20 @@ The name of the autocreated ActionType (d24ede5f-4064-4898-bb84-cfb533b1fbc0)Setze boll Wert
-
+ Timeout actionThe name of the ActionType 54646e7c-bc54-4895-81a2-590d72d120f9 of deviceClass Mock Device (Push Button)Timeout Aktion
-
+ Mock Device (Display Pin)The name of the DeviceClass (296f1fd4-e893-46b2-8a42-50d1bceb8730)Mock Gerät (Pin anzeigen)
-
-
+
+ bool value changedThe name of the autocreated EventType (e680f7a4-b39e-46da-be41-fa3170fe3768)
----------
@@ -264,8 +264,8 @@ The name of the autocreated EventType (d24ede5f-4064-4898-bb84-cfb533b1fbc0)Bool Wert geändert
-
-
+
+ bool valueThe name of the ParamType of StateType (e680f7a4-b39e-46da-be41-fa3170fe3768) of DeviceClass Mock Device (Push Button)
----------
@@ -273,97 +273,97 @@ The name of the ParamType of StateType (d24ede5f-4064-4898-bb84-cfb533b1fbc0) of
Bool Wert
-
+ Text lineThe name of the paramType (e6acf0c7-4b8e-4296-ac62-855d20deb816) of Mock Device (InputTypes)Textzeile
-
+ Text areaThe name of the paramType (716f0994-bc01-42b0-b64d-59236f7320d2) of Mock Device (InputTypes)Textfeld
-
+ Password textThe name of the paramType (e5c0d14b-c9f1-4aca-a56e-85bfa6977150) of Mock Device (InputTypes)Passwort Text
-
+ Search textThe name of the paramType (22add8c9-ee4f-43ad-8931-58e999313ac3) of Mock Device (InputTypes)Suchtext
-
+ Mail addressThe name of the paramType (a8494faf-3a0f-4cf3-84b7-4b39148a838d) of Mock Device (InputTypes)Mail Adresse
-
+ IPv4 addressThe name of the paramType (9e5f86a0-4bb3-4892-bff8-3fc4032af6e2) of Mock Device (InputTypes)IPv4 Adresse
-
+ IPv6 addressThe name of the paramType (43bf3832-dd48-4090-a836-656e8b60216e) of Mock Device (InputTypes)IPv6 Adresse
-
+ URLThe name of the paramType (fa67229f-fcef-496f-b671-59a4b48f3ab5) of Mock Device (InputTypes)URL
-
+ Mac addressThe name of the paramType (e93db587-7919-48f3-8c88-1651de63c765) of Mock Device (InputTypes)Mac Adresse
-
+ Please enter the secret which normaly will be displayed on the device. For the mockdevice the pin is 243681.The pairing info of deviceClass Mock Device (Display Pin)Bitte geben sie den Pincode ein der normalerweise auf dem Gerät angezeit werden würde. In diesem fall lautet der Pincode 243681.
-
+ Mock DevicesThe name of the plugin Mock Devices (727a4a9a-c187-446f-aadf-f1b2220607d1)Mock Gerät
-
+ pinThe name of the paramType (da820e07-22dc-4173-9c07-2f49a4e265f9) of Mock Device (Display Pin)Pin
-
+ Mock Device (Parent)The name of the DeviceClass (a71fbde9-9a38-4bf8-beab-c8aade2608ba)Mock Gerät (Elternteil)
-
+ Mock Device (Child)The name of the DeviceClass (40893c9f-bc47-40c1-8bf7-b390c7c1b4fc)Mock Gerät (Kind)
-
+ parent uuidThe name of the paramType (104b5288-404e-42d3-bf38-e40682e75681) of Mock Device (Child)Elternteil Uuid
-
+ Mock Device (InputTypes)The name of the DeviceClass (515ffdf1-55e5-498d-9abc-4e2fe768f3a9)Mock Gerät (Eingabemethoden)
diff --git a/plugins/mock/translations/en_US.ts b/plugins/mock/translations/en_US.ts
index 72bacc14..adf3e165 100644
--- a/plugins/mock/translations/en_US.ts
+++ b/plugins/mock/translations/en_US.ts
@@ -12,230 +12,230 @@
MockDevice
-
+ guhThe name of the vendor (2062d64d-3232-433c-88bc-0d33c0ba2ba6)
-
+ Mock DeviceThe name of the DeviceClass (753f0d32-0468-4d08-82ed-1964aab03298)
-
+ http portThe name of the paramType (d4f06047-125e-4479-9810-b54c189917f5) of Mock Device
-
+ Mock Action 3 (async)The name of the ActionType fbae06d3-7666-483e-a39e-ec50fe89054e of deviceClass Mock Device
-
+ Mock Action 4 (broken)The name of the ActionType df3cf33d-26d5-4577-9132-9823bd33fad0 of deviceClass Mock Device
-
+ Mock Action 5 (async, broken)The name of the ActionType bfe89a1d-3497-4121-8318-e77c37537219 of deviceClass Mock Device
-
+ Mock Device (Auto created)The name of the DeviceClass (ab4257b3-7548-47ee-9bd4-7dc3004fd197)
-
+ Mock Device (Push Button)The name of the DeviceClass (9e03144c-e436-4eea-82d9-ccb33ef778db)
-
+ Wait 3 second before you continue, the push button will be pressed automatically.The pairing info of deviceClass Mock Device (Push Button)
-
+ configParamIntThe name of the paramType (e1f72121-a426-45e2-b475-8262b5cdf103) of Mock Devices
-
+ configParamBoolThe name of the paramType (c75723b6-ea4f-4982-9751-6c5e39c88145) of Mock Devices
-
+ asyncThe name of the paramType (f2977061-4dd0-4ef5-85aa-3b7134743be3) of Mock Device
-
+ brokenThe name of the paramType (ae8f8901-f2c1-42a5-8111-6d2fc8e4c1e4) of Mock Device
-
+ resultCountThe name of the paramType (d222adb4-2f9c-4c3f-8655-76400d0fb6ce) of Mock Device
-
+ Dummy int state changedThe name of the autocreated EventType (80baec19-54de-4948-ac46-31eabfaceb83)
-
+ Dummy int stateThe name of the ParamType of StateType (80baec19-54de-4948-ac46-31eabfaceb83) of DeviceClass Mock Device
-
+ Dummy bool state changedThe name of the autocreated EventType (9dd6a97c-dfd1-43dc-acbd-367932742310)
-
+ Dummy bool stateThe name of the ParamType of StateType (9dd6a97c-dfd1-43dc-acbd-367932742310) of DeviceClass Mock Device
-
+ Mock Action 1 (with params)The name of the ActionType dea0f4e1-65e3-4981-8eaa-2701c53a9185 of deviceClass Mock Device
-
+ mockActionParam1The name of the paramType (a2d3a256-a551-4712-a65b-ecd5a436a1cb) of Mock Device
-
+ mockActionParam2The name of the paramType (304a4899-18be-4e3b-94f4-d03be52f3233) of Mock Device
-
+ Mock Action 2 (without params)The name of the ActionType defd3ed6-1a0d-400b-8879-a0202cf39935 of deviceClass Mock Device
-
+ mockParamIntThe name of the paramType (0550e16d-60b9-4ba5-83f4-4d3cee656121) of Mock Device
-
+ Mock Event 1The name of the EventType 45bf3752-0fc6-46b9-89fd-ffd878b5b22b of deviceClass Mock Device (Auto created)
-
+ Mock Event 2The name of the EventType 863d5920-b1cf-4eb9-88bd-8f7b8583b1cf of deviceClass Mock Device (Auto created)
-
+ color changedThe name of the autocreated EventType (20dc7c22-c50e-42db-837c-2bbced939f8e)
-
+ colorThe name of the ParamType of StateType (20dc7c22-c50e-42db-837c-2bbced939f8e) of DeviceClass Mock Device (Push Button)
-
+ Set colorThe name of the autocreated ActionType (20dc7c22-c50e-42db-837c-2bbced939f8e)
-
+ percentage changedThe name of the autocreated EventType (72981c04-267a-4ba0-a59e-9921d2f3af9c)
-
+ percentageThe name of the ParamType of StateType (72981c04-267a-4ba0-a59e-9921d2f3af9c) of DeviceClass Mock Device (Push Button)
-
+ Set percentageThe name of the autocreated ActionType (72981c04-267a-4ba0-a59e-9921d2f3af9c)
-
+ allowed values changedThe name of the autocreated EventType (05f63f9c-f61e-4dcf-ad55-3f13fde2765b)
-
+ allowed valuesThe name of the ParamType of StateType (05f63f9c-f61e-4dcf-ad55-3f13fde2765b) of DeviceClass Mock Device (Push Button)
-
+ Set allowed valuesThe name of the autocreated ActionType (05f63f9c-f61e-4dcf-ad55-3f13fde2765b)
-
+ double value changedThe name of the autocreated EventType (53cd7c55-49b7-441b-b970-9048f20f0e2c)
-
+ double valueThe name of the ParamType of StateType (53cd7c55-49b7-441b-b970-9048f20f0e2c) of DeviceClass Mock Device (Push Button)
-
+ Set double valueThe name of the autocreated ActionType (53cd7c55-49b7-441b-b970-9048f20f0e2c)
-
-
+
+ Set bool valueThe name of the autocreated ActionType (e680f7a4-b39e-46da-be41-fa3170fe3768)
----------
@@ -243,20 +243,20 @@ The name of the autocreated ActionType (d24ede5f-4064-4898-bb84-cfb533b1fbc0)
-
+ Timeout actionThe name of the ActionType 54646e7c-bc54-4895-81a2-590d72d120f9 of deviceClass Mock Device (Push Button)
-
+ Mock Device (Display Pin)The name of the DeviceClass (296f1fd4-e893-46b2-8a42-50d1bceb8730)
-
-
+
+ bool value changedThe name of the autocreated EventType (e680f7a4-b39e-46da-be41-fa3170fe3768)
----------
@@ -264,8 +264,8 @@ The name of the autocreated EventType (d24ede5f-4064-4898-bb84-cfb533b1fbc0)
-
-
+
+ bool valueThe name of the ParamType of StateType (e680f7a4-b39e-46da-be41-fa3170fe3768) of DeviceClass Mock Device (Push Button)
----------
@@ -273,97 +273,97 @@ The name of the ParamType of StateType (d24ede5f-4064-4898-bb84-cfb533b1fbc0) of
-
+ Text lineThe name of the paramType (e6acf0c7-4b8e-4296-ac62-855d20deb816) of Mock Device (InputTypes)
-
+ Text areaThe name of the paramType (716f0994-bc01-42b0-b64d-59236f7320d2) of Mock Device (InputTypes)
-
+ Password textThe name of the paramType (e5c0d14b-c9f1-4aca-a56e-85bfa6977150) of Mock Device (InputTypes)
-
+ Search textThe name of the paramType (22add8c9-ee4f-43ad-8931-58e999313ac3) of Mock Device (InputTypes)
-
+ Mail addressThe name of the paramType (a8494faf-3a0f-4cf3-84b7-4b39148a838d) of Mock Device (InputTypes)
-
+ IPv4 addressThe name of the paramType (9e5f86a0-4bb3-4892-bff8-3fc4032af6e2) of Mock Device (InputTypes)
-
+ IPv6 addressThe name of the paramType (43bf3832-dd48-4090-a836-656e8b60216e) of Mock Device (InputTypes)
-
+ URLThe name of the paramType (fa67229f-fcef-496f-b671-59a4b48f3ab5) of Mock Device (InputTypes)
-
+ Mac addressThe name of the paramType (e93db587-7919-48f3-8c88-1651de63c765) of Mock Device (InputTypes)
-
+ Please enter the secret which normaly will be displayed on the device. For the mockdevice the pin is 243681.The pairing info of deviceClass Mock Device (Display Pin)
-
+ Mock DevicesThe name of the plugin Mock Devices (727a4a9a-c187-446f-aadf-f1b2220607d1)
-
+ pinThe name of the paramType (da820e07-22dc-4173-9c07-2f49a4e265f9) of Mock Device (Display Pin)
-
+ Mock Device (Parent)The name of the DeviceClass (a71fbde9-9a38-4bf8-beab-c8aade2608ba)
-
+ Mock Device (Child)The name of the DeviceClass (40893c9f-bc47-40c1-8bf7-b390c7c1b4fc)
-
+ parent uuidThe name of the paramType (104b5288-404e-42d3-bf38-e40682e75681) of Mock Device (Child)
-
+ Mock Device (InputTypes)The name of the DeviceClass (515ffdf1-55e5-498d-9abc-4e2fe768f3a9)
diff --git a/server/main.cpp b/server/main.cpp
index 0ef69274..561e5b16 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -38,6 +38,7 @@
#include "guhcore.h"
#include "guhservice.h"
#include "guhsettings.h"
+#include "guhdbusservice.h"
#include "guhapplication.h"
#include "loggingcategories.h"
@@ -195,6 +196,9 @@ int main(int argc, char *argv[])
QCommandLineOption logOption({"l", "log"}, QCoreApplication::translate("main", "Specify a log file to write to, If this option is not specified, logs will be printed to the standard output."), "logfile", "/var/log/guhd.log");
parser.addOption(logOption);
+ QCommandLineOption dbusOption(QStringList() << "session", QCoreApplication::translate("main", "If specified, all D-Bus interfaces will be bound to the session bus instead of the system bus."));
+ parser.addOption(dbusOption);
+
parser.process(application);
// Open the logfile, if any specified
@@ -234,6 +238,10 @@ int main(int argc, char *argv[])
}
QLoggingCategory::installFilter(loggingCategoryFilter);
+ if (parser.isSet(dbusOption)) {
+ GuhDBusService::setBusType(QDBusConnection::SessionBus);
+ }
+
bool startForeground = parser.isSet(foregroundOption);
if (startForeground) {
// inform about userid
diff --git a/tests/auto/api.json b/tests/auto/api.json
index a850211a..7552ac96 100644
--- a/tests/auto/api.json
+++ b/tests/auto/api.json
@@ -1,4 +1,4 @@
-0.53
+0.54
{
"methods": {
"Actions.ExecuteAction": {
@@ -403,7 +403,7 @@
}
},
"JSONRPC.Authenticate": {
- "description": "Authenticate a client to the api. This will return a new token to be used to authorize a client at the API.",
+ "description": "Authenticate a client to the api via user & password challenge. Provide a device name which allows the user to identify the client and revoke the token in case the device is lost or stolen. This will return a new token to be used to authorize a client at the API.",
"params": {
"deviceName": "String",
"password": "String",
@@ -411,7 +411,7 @@
},
"returns": {
"o:token": "String",
- "success": "String"
+ "success": "Bool"
}
},
"JSONRPC.CreateUser": {
@@ -435,6 +435,7 @@
"language": "String",
"name": "String",
"protocol version": "String",
+ "pushButtonAuthAvailable": "Bool",
"server": "String",
"uuid": "Uuid",
"version": "String"
@@ -475,6 +476,16 @@
"error": "$ref:UserError"
}
},
+ "JSONRPC.RequestPushButtonAuth": {
+ "description": "Authenticate a client to the api via Push Button method. Provide a device name which allows the user to identify the client and revoke the token in case the device is lost or stolen. If push button hardware is available, this will return with success and start listening for push button presses. When the push button is pressed, the PushButtonAuthFinished notification will be sent to the requesting client. The procedure will be cancelled when the connection is interrupted. If another client requests push button authentication while a procedure is still going on, the second call will take over and the first one will be notified by the PushButtonAuthFinished signal about the error. The application should make it clear to the user to not press the button when the procedure fails as this can happen for 2 reasons: a) a second user is trying to auth at the same time and only the currently active user should press the button or b) it might indicate an attacker trying to take over and snooping in for tokens.",
+ "params": {
+ "deviceName": "String"
+ },
+ "returns": {
+ "success": "Bool",
+ "transactionId": "Int"
+ }
+ },
"JSONRPC.SetNotificationStatus": {
"description": "Enable/Disable notifications for this connections.",
"params": {
@@ -496,7 +507,7 @@
}
},
"JSONRPC.Tokens": {
- "description": "Return a list of if TokenInfo objects all the tokens for the current user.",
+ "description": "Return a list of TokenInfo objects of all the tokens for the current user.",
"params": {
},
"returns": {
@@ -850,6 +861,14 @@
"connected": "Bool"
}
},
+ "JSONRPC.PushButtonAuthFinished": {
+ "description": "Emitted when a push button authentication reaches final state. NOTE: This notification is special. It will only be emitted to connections that did actively request a push button authentication, but also it will be emitted regardless of the notification settings. ",
+ "params": {
+ "o:token": "String",
+ "status": "$ref:UserError",
+ "transactionId": "Int"
+ }
+ },
"Logging.LogDatabaseUpdated": {
"description": "Emitted whenever the database was updated. The database will be updated when a log entry was deleted. A log entry will be deleted when the corresponding device or a rule will be removed, or when the oldest entry of the database was deleted to keep to database in the size limits.",
"params": {
diff --git a/tests/auto/guhtestbase.cpp b/tests/auto/guhtestbase.cpp
index 7ca064ba..e57c529d 100644
--- a/tests/auto/guhtestbase.cpp
+++ b/tests/auto/guhtestbase.cpp
@@ -199,6 +199,7 @@ void GuhTestBase::initTestCase()
// Add the mockdevice
m_mockTcpServer = MockTcpServer::servers().first();
m_clientId = QUuid::createUuid();
+ m_mockTcpServer->clientConnected(m_clientId);
createMockDevice();
@@ -223,7 +224,7 @@ void GuhTestBase::cleanup()
}
}
-QVariant GuhTestBase::injectAndWait(const QString &method, const QVariantMap ¶ms)
+QVariant GuhTestBase::injectAndWait(const QString &method, const QVariantMap ¶ms, const QUuid &clientId)
{
QVariantMap call;
call.insert("id", m_commandId);
@@ -234,7 +235,7 @@ QVariant GuhTestBase::injectAndWait(const QString &method, const QVariantMap &pa
QJsonDocument jsonDoc = QJsonDocument::fromVariant(call);
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
- m_mockTcpServer->injectData(m_clientId, jsonDoc.toJson(QJsonDocument::Compact));
+ m_mockTcpServer->injectData(clientId.isNull() ? m_clientId : clientId, jsonDoc.toJson(QJsonDocument::Compact));
if (spy.count() == 0) {
spy.wait();
@@ -499,6 +500,7 @@ void GuhTestBase::restartServer()
QSignalSpy spy(GuhCore::instance()->deviceManager(), SIGNAL(loaded()));
spy.wait();
m_mockTcpServer = MockTcpServer::servers().first();
+ m_mockTcpServer->clientConnected(m_clientId);
}
void GuhTestBase::clearLoggingDatabase()
diff --git a/tests/auto/guhtestbase.h b/tests/auto/guhtestbase.h
index fdae7a27..4f379b8b 100644
--- a/tests/auto/guhtestbase.h
+++ b/tests/auto/guhtestbase.h
@@ -104,7 +104,7 @@ protected slots:
void cleanup();
protected:
- QVariant injectAndWait(const QString &method, const QVariantMap ¶ms = QVariantMap());
+ QVariant injectAndWait(const QString &method, const QVariantMap ¶ms = QVariantMap(), const QUuid &clientId = QUuid());
QVariant checkNotification(const QSignalSpy &spy, const QString ¬ification);
QVariantList checkNotifications(const QSignalSpy &spy, const QString ¬ification);
diff --git a/tests/auto/jsonrpc/jsonrpc.pro b/tests/auto/jsonrpc/jsonrpc.pro
index a93e7d16..b239ef31 100644
--- a/tests/auto/jsonrpc/jsonrpc.pro
+++ b/tests/auto/jsonrpc/jsonrpc.pro
@@ -2,4 +2,7 @@ include(../../../guh.pri)
include(../autotests.pri)
TARGET = testjsonrpc
-SOURCES += testjsonrpc.cpp
+SOURCES += testjsonrpc.cpp \
+ ../../utils/pushbuttonagent.cpp
+
+HEADERS += ../../utils/pushbuttonagent.h
diff --git a/tests/auto/jsonrpc/testjsonrpc.cpp b/tests/auto/jsonrpc/testjsonrpc.cpp
index ef9a6ded..eecdefc1 100644
--- a/tests/auto/jsonrpc/testjsonrpc.cpp
+++ b/tests/auto/jsonrpc/testjsonrpc.cpp
@@ -23,6 +23,7 @@
#include "guhcore.h"
#include "devicemanager.h"
#include "mocktcpserver.h"
+#include "../../utils/pushbuttonagent.h"
#include
#include
@@ -65,6 +66,39 @@ private slots:
void pluginConfigChangeEmitsNotification();
+ /*
+ Cases for push button auth:
+
+ Case 1: regular pushbutton
+ - alice sends JSONRPC.RequestPushButtonAuth, gets "OK" back (if push button hardware is available)
+ - alice pushes the hardware button and gets a notification on jsonrpc containing the token for local auth
+ */
+ void testPushButtonAuth();
+
+ /*
+ Case 2: if we have an attacker in the network, he could try to call requestPushButtonAuth and
+ hope someone would eventually press the button and give him a token. In order to prevent this
+ any previous attempt for a push button auth needs to be cancelled when a new request comes in:
+
+ * Mallory does RequestPushButtonAuth, gets OK back
+ * Alice does RequestPushButtonAuth,
+ * Mallory receives a "PushButtonFailed" notification
+ * Alice receives OK
+ * alice presses the hardware button
+ * Alice reveices a notification with token, mallory receives nothing
+
+ Case 3: Mallory tries to hijack it back again
+
+ * Mallory does RequestPushButtonAuth, gets OK back
+ * Alice does RequestPusButtonAuth,
+ * Alice gets ok reply, Mallory gets failed notification
+ * Mallory quickly does RequestPushButtonAuth again to win the fight
+ * Alice gets failed notification and can instruct the user to _not_ press the button now until procedure is restarted
+ */
+ void testPushButtonAuthInterrupt();
+
+ void testPushButtonAuthConnectionDrop();
+
private:
QStringList extractRefs(const QVariant &variant);
@@ -109,12 +143,22 @@ void TestJSONRPC::testHandshake()
QString guhVersionString(GUH_VERSION_STRING);
QVERIFY2(handShake.value("version").toString() == guhVersionString, "Handshake version doesn't match Guh version.");
+ // Check whether pushButtonAuth is disabled
+ QCOMPARE(handShake.value("pushButtonAuthAvailable").toBool(), false);
+
+ // Now register push button agent
+ PushButtonAgent pushButtonAgent;
+ pushButtonAgent.init();
+
// And now check if it is sent again when calling JSONRPC.Hello
handShake = injectAndWait("JSONRPC.Hello").toMap();
QCOMPARE(handShake.value("params").toMap().value("version").toString(), guhVersionString);
m_mockTcpServer->clientDisconnected(newClientId);
+ // Check whether pushButtonAuth is now
+ QCOMPARE(handShake.value("params").toMap().value("pushButtonAuthAvailable").toBool(), true);
+
// And now check if it is sent again when calling JSONRPC.Hello
handShake = injectAndWait("JSONRPC.Hello").toMap();
QCOMPARE(handShake.value("params").toMap().value("version").toString(), guhVersionString);
@@ -876,6 +920,188 @@ void TestJSONRPC::pluginConfigChangeEmitsNotification()
QCOMPARE(notificationData.first().toMap().value("notification").toString() == "Devices.PluginConfigurationChanged", true);
}
+void TestJSONRPC::testPushButtonAuth()
+{
+ PushButtonAgent pushButtonAgent;
+ pushButtonAgent.init();
+
+ QVariantMap params;
+ params.insert("deviceName", "pbtestdevice");
+ QVariant response = injectAndWait("JSONRPC.RequestPushButtonAuth", params);
+ QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
+ int transactionId = response.toMap().value("params").toMap().value("transactionId").toInt();
+
+ // Setup connection to mock client
+ QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
+
+ pushButtonAgent.sendButtonPressed();
+
+ if (clientSpy.count() == 0) {
+ clientSpy.wait();
+ }
+ QVariantMap rsp = checkNotification(clientSpy, "JSONRPC.PushButtonAuthFinished").toMap();
+
+ QCOMPARE(rsp.value("params").toMap().value("transactionId").toInt(), transactionId);
+ QVERIFY2(!rsp.value("params").toMap().value("token").toByteArray().isEmpty(), "Token not in push button auth notification");
+}
+
+
+
+void TestJSONRPC::testPushButtonAuthInterrupt()
+{
+ PushButtonAgent pushButtonAgent;
+ pushButtonAgent.init();
+
+ // m_clientId is registered in gutTestbase already, just using it here to improve readability of the test
+ QUuid aliceId = m_clientId;
+
+ // Create a new clientId for mallory and connect it to the server
+ QUuid malloryId = QUuid::createUuid();
+ m_mockTcpServer->clientConnected(malloryId);
+
+ // Snoop in on everything the TCP server sends to its clients.
+ QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
+
+ // request push button auth for client 1 (alice) and check for OK reply
+ QVariantMap params;
+ params.insert("deviceName", "alice");
+ QVariant response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, aliceId);
+ QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
+ int transactionId1 = response.toMap().value("params").toMap().value("transactionId").toInt();
+
+
+ // Request push button auth for client 2 (mallory)
+ clientSpy.clear();
+ params.clear();
+ params.insert("deviceName", "mallory");
+ response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, malloryId);
+ QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
+ int transactionId2 = response.toMap().value("params").toMap().value("transactionId").toInt();
+
+ // Both clients should receive something. Wait for it
+ if (clientSpy.count() < 2) {
+ clientSpy.wait();
+ }
+
+ // spy.at(0) should be the failed notification for alice
+ // spy.at(1) shpuld be the OK reply for mallory
+
+
+ // alice should have received a failed notification. She knows something's wrong.
+ QVariantMap notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
+ QCOMPARE(clientSpy.first().first().toUuid(), aliceId);
+ QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
+ QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId1);
+ QCOMPARE(notification.value("params").toMap().value("success").toBool(), false);
+
+ // Mallory instead should have received an OK
+ QVariantMap reply = QJsonDocument::fromJson(clientSpy.at(1).at(1).toByteArray()).toVariant().toMap();
+ QCOMPARE(clientSpy.at(1).first().toUuid(), malloryId);
+ QCOMPARE(reply.value("params").toMap().value("success").toBool(), true);
+
+
+ // Alice tries once more
+ clientSpy.clear();
+ params.clear();
+ params.insert("deviceName", "alice");
+ response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, aliceId);
+ QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
+ int transactionId3 = response.toMap().value("params").toMap().value("transactionId").toInt();
+
+ // Both clients should receive something. Wait for it
+ if (clientSpy.count() < 2) {
+ clientSpy.wait();
+ }
+
+ // spy.at(0) should be the failed notification for mallory
+ // spy.at(1) shpuld be the OK reply for alice
+
+ // mallory should have received a failed notification. She knows something's wrong.
+ notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
+ QCOMPARE(clientSpy.first().first().toUuid(), malloryId);
+ QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
+ QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId2);
+ QCOMPARE(notification.value("params").toMap().value("success").toBool(), false);
+
+ // Alice instead should have received an OK
+ reply = QJsonDocument::fromJson(clientSpy.at(1).at(1).toByteArray()).toVariant().toMap();
+ QCOMPARE(clientSpy.at(1).first().toUuid(), aliceId);
+ QCOMPARE(reply.value("params").toMap().value("success").toBool(), true);
+
+ clientSpy.clear();
+
+ // do the button press
+ pushButtonAgent.sendButtonPressed();
+
+ // Wait for things to happen
+ if (clientSpy.count() == 0) {
+ clientSpy.wait();
+ }
+
+ // There should have been only exactly one message sent, the token for alice
+ // Mallory should not have received anything
+ QCOMPARE(clientSpy.count(), 1);
+ notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
+ QCOMPARE(clientSpy.first().first().toUuid(), aliceId);
+ QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
+ QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId3);
+ QCOMPARE(notification.value("params").toMap().value("success").toBool(), true);
+ QVERIFY2(!notification.value("params").toMap().value("token").toByteArray().isEmpty(), "Token is empty while it shouldn't be");
+}
+
+void TestJSONRPC::testPushButtonAuthConnectionDrop()
+{
+ PushButtonAgent pushButtonAgent;
+ pushButtonAgent.init();
+
+ // Snoop in on everything the TCP server sends to its clients.
+ QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
+
+ // Create a new clientId for alice and connect it to the server
+ QUuid aliceId = QUuid::createUuid();
+ m_mockTcpServer->clientConnected(aliceId);
+
+ // request push button auth for client 1 (alice) and check for OK reply
+ QVariantMap params;
+ params.insert("deviceName", "alice");
+ QVariant response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, aliceId);
+ QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
+
+ // Disconnect alice
+ m_mockTcpServer->clientDisconnected(aliceId);
+
+ // Now try with bob
+ // Create a new clientId for bob and connect it to the server
+ QUuid bobId = QUuid::createUuid();
+ m_mockTcpServer->clientConnected(bobId);
+
+ // request push button auth for client 1 (alice) and check for OK reply
+ params.clear();
+ params.insert("deviceName", "bob");
+ response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, bobId);
+ QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
+ int transactionId = response.toMap().value("params").toMap().value("transactionId").toInt();
+
+ clientSpy.clear();
+
+ pushButtonAgent.sendButtonPressed();
+
+ // Wait for things to happen
+ if (clientSpy.count() == 0) {
+ clientSpy.wait();
+ }
+
+ // There should have been only exactly one message sent, the token for bob
+ QCOMPARE(clientSpy.count(), 1);
+ QVariantMap notification = QJsonDocument::fromJson(clientSpy.first().at(1).toByteArray()).toVariant().toMap();
+ QCOMPARE(clientSpy.first().first().toUuid(), bobId);
+ QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
+ QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId);
+ QCOMPARE(notification.value("params").toMap().value("success").toBool(), true);
+ QVERIFY2(!notification.value("params").toMap().value("token").toByteArray().isEmpty(), "Token is empty while it shouldn't be");
+
+}
+
#include "testjsonrpc.moc"
QTEST_MAIN(TestJSONRPC)
diff --git a/tests/libguh-core/libguh-core.pro b/tests/libguh-core/libguh-core.pro
deleted file mode 100644
index 43223ff4..00000000
--- a/tests/libguh-core/libguh-core.pro
+++ /dev/null
@@ -1,19 +0,0 @@
-include(../../guh.pri)
-include(../../server/server.pri)
-
-TARGET = guh-core
-TEMPLATE = lib
-
-QT += sql
-
-DEFINES += TESTING_ENABLED
-
-INCLUDEPATH += $$top_srcdir/server/ \
- $$top_srcdir/server/jsonrpc \
- $$top_srcdir/libguh \
- $$top_srcdir/tests/auto/
-
-LIBS += -L$$top_builddir/libguh/ -lguh -lssl -lcrypto -laws-iot-sdk-cpp -lmbedtls -lmbedx509 -lmbedcrypto
-
-target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')
-INSTALLS += target
diff --git a/tests/scripts/getconfig.sh b/tests/scripts/getconfig.sh
new file mode 100755
index 00000000..82634ffe
--- /dev/null
+++ b/tests/scripts/getconfig.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+if [ -z $1 ]; then
+ echo "usage $0 host"
+else
+ (echo '{"id":1, "method":"Configuration.GetConfigurations"}'; sleep 1) | nc $1 2222
+fi
diff --git a/tests/scripts/hello.sh b/tests/scripts/hello.sh
new file mode 100755
index 00000000..6e7f2155
--- /dev/null
+++ b/tests/scripts/hello.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+if [ -z $1 ]; then
+ echo "usage: $0 host"
+else
+ (echo '{"id":1, "method": "JSONRPC.Hello"}'; sleep 1) | nc $1 2222
+fi
diff --git a/tests/scripts/pushbuttonauthenticate.sh b/tests/scripts/pushbuttonauthenticate.sh
new file mode 100755
index 00000000..bc63d546
--- /dev/null
+++ b/tests/scripts/pushbuttonauthenticate.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+if [ -z $2 ]; then
+ echo "usage: $0 host devicename"
+else
+ (echo '{"id":1, "method": "JSONRPC.RequestPushButtonAuth", "params": { "deviceName": "'$2'"}}'; sleep 1) | nc $1 2222
+fi
diff --git a/tests/tests.pro b/tests/tests.pro
index 2ede5161..06c1e4b4 100644
--- a/tests/tests.pro
+++ b/tests/tests.pro
@@ -1,3 +1,3 @@
TEMPLATE = subdirs
-SUBDIRS = auto
+SUBDIRS = auto tools/simplepushbuttonhandler
diff --git a/tests/tools/simplepushbuttonhandler/inputwatcher.cpp b/tests/tools/simplepushbuttonhandler/inputwatcher.cpp
new file mode 100644
index 00000000..24bcfbda
--- /dev/null
+++ b/tests/tools/simplepushbuttonhandler/inputwatcher.cpp
@@ -0,0 +1,40 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "inputwatcher.h"
+
+#include
+#include
+#include
+
+#include
+
+InputWatcher::InputWatcher(QObject *parent) : QObject(parent)
+{
+ m_notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this);
+ connect(m_notifier, SIGNAL(activated(int)), this, SLOT(readyRead()));
+}
+
+void InputWatcher::readyRead()
+{
+ QTextStream qin(stdin);
+ qin.readLine();
+ emit enterPressed();
+}
diff --git a/tests/tools/simplepushbuttonhandler/inputwatcher.h b/tests/tools/simplepushbuttonhandler/inputwatcher.h
new file mode 100644
index 00000000..62321fac
--- /dev/null
+++ b/tests/tools/simplepushbuttonhandler/inputwatcher.h
@@ -0,0 +1,44 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INPUTWATCHER_H
+#define INPUTWATCHER_H
+
+#include
+
+class QSocketNotifier;
+
+class InputWatcher : public QObject
+{
+ Q_OBJECT
+public:
+ explicit InputWatcher(QObject *parent = nullptr);
+
+private slots:
+ void readyRead();
+
+signals:
+ void enterPressed();
+
+private:
+ QSocketNotifier *m_notifier;
+};
+
+#endif // INPUTWATCHER_H
diff --git a/tests/tools/simplepushbuttonhandler/main.cpp b/tests/tools/simplepushbuttonhandler/main.cpp
new file mode 100644
index 00000000..7172d238
--- /dev/null
+++ b/tests/tools/simplepushbuttonhandler/main.cpp
@@ -0,0 +1,47 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include
+#include
+#include
+#include
+
+#include "../../utils/pushbuttonagent.h"
+#include "inputwatcher.h"
+
+int main(int argc, char *argv[])
+{
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ QCommandLineOption dbusOption(QStringList() << "session", QCoreApplication::translate("main", "If specified, all D-Bus interfaces will be bound to the session bus instead of the system bus."));
+ parser.addOption(dbusOption);
+
+ QCoreApplication a(argc, argv);
+
+ PushButtonAgent agent;
+ if (!agent.init(parser.isSet(dbusOption) ? QDBusConnection::SessionBus : QDBusConnection::SystemBus)) {
+ return -1;
+ }
+ InputWatcher inputWatcher;
+ QObject::connect(&inputWatcher, &InputWatcher::enterPressed, &agent, &PushButtonAgent::sendButtonPressed);
+
+ qDebug() << "Use the Enter key as push button.";
+ return a.exec();
+}
diff --git a/tests/tools/simplepushbuttonhandler/simplepushbuttonhandler.pro b/tests/tools/simplepushbuttonhandler/simplepushbuttonhandler.pro
new file mode 100644
index 00000000..0ac2c7e6
--- /dev/null
+++ b/tests/tools/simplepushbuttonhandler/simplepushbuttonhandler.pro
@@ -0,0 +1,24 @@
+QT -= gui
+QT += dbus
+
+CONFIG += c++11 console
+CONFIG -= app_bundle
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which as been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += main.cpp \
+ ../../utils/pushbuttonagent.cpp \
+ inputwatcher.cpp
+
+HEADERS += \
+ ../../utils/pushbuttonagent.h \
+ inputwatcher.h
diff --git a/tests/utils/pushbuttonagent.cpp b/tests/utils/pushbuttonagent.cpp
new file mode 100644
index 00000000..3c7b2eb5
--- /dev/null
+++ b/tests/utils/pushbuttonagent.cpp
@@ -0,0 +1,57 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "pushbuttonagent.h"
+
+#include
+#include
+#include
+#include
+
+PushButtonAgent::PushButtonAgent(QObject *parent) : QObject(parent)
+{
+}
+
+bool PushButtonAgent::init(QDBusConnection::BusType busType)
+{
+ QDBusConnection bus = busType == QDBusConnection::SessionBus ? QDBusConnection::sessionBus() : QDBusConnection::systemBus();
+
+ bool result = bus.registerObject("/guh/pushbuttonhandler", this, QDBusConnection::ExportScriptableContents);
+ if (!result) {
+ qDebug() << "Error registering PushButton agent on D-Bus.";
+ return false;
+ }
+
+ QDBusMessage message = QDBusMessage::createMethodCall("io.guh.nymead", "/io/guh/nymead/UserManager", QString(), "RegisterButtonAgent");
+ message << qVariantFromValue(QDBusObjectPath("/guh/pushbuttonhandler"));
+ QDBusMessage reply = bus.call(message);
+ if (!reply.errorName().isEmpty()) {
+ qDebug() << "Error registering PushButton agent:" << reply.errorMessage();
+ return false;
+ }
+ qDebug() << "PushButton agent registered.";
+ return true;
+}
+
+void PushButtonAgent::sendButtonPressed()
+{
+ qDebug() << "Sending button pressed event.";
+ emit PushButtonPressed();
+}
diff --git a/tests/utils/pushbuttonagent.h b/tests/utils/pushbuttonagent.h
new file mode 100644
index 00000000..9e411f22
--- /dev/null
+++ b/tests/utils/pushbuttonagent.h
@@ -0,0 +1,42 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2017 Michael Zanetti *
+ * *
+ * This file is part of guh. *
+ * *
+ * Guh is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * Guh is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with guh. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef PUSHBUTTONAGENT_H
+#define PUSHBUTTONAGENT_H
+
+#include
+#include
+
+class PushButtonAgent : public QObject
+{
+ Q_OBJECT
+public:
+ explicit PushButtonAgent(QObject *parent = nullptr);
+
+ bool init(QDBusConnection::BusType busType = QDBusConnection::SystemBus);
+
+signals:
+ Q_SCRIPTABLE void PushButtonPressed();
+
+public slots:
+ void sendButtonPressed();
+};
+
+#endif // PUSHBUTTONAGENT_H
diff --git a/translations/guhd-de_DE.ts b/translations/guhd-de_DE.ts
index c7671883..5b6e744a 100644
--- a/translations/guhd-de_DE.ts
+++ b/translations/guhd-de_DE.ts
@@ -4,7 +4,7 @@
main
-
+
guh ( /[guːh]/ ) is an open source IoT (Internet of Things) server,
which allows to control a lot of different devices from many different
@@ -23,12 +23,12 @@ Szenen undVerhaltensweisen des Systems festzulegen.
-
+ Run guhd in the foreground, not as daemon.Starte guhd im Vordergrund, nicht als Service.
-
+ Debug categories to enable. Prefix with "No" to disable. Warnings from all categories will be printed unless explicitly muted with "NoWarnings".
Categories are:
@@ -36,17 +36,22 @@ Categories are:
Es gibt folgende Kategorien:
-
+ Enables all debug categories. This parameter overrides all debug category parameters.Aktiviere alle Debug-Kategorien. Dieser Parameter überschreibt alle anderen Debug-Kategorien Parameter.
-
+ Specify a log file to write to, If this option is not specified, logs will be printed to the standard output.
-
+
+ If specified, all D-Bus interfaces will be bound to the session bus instead of the system bus.
+
+
+
+ No such debug category:Diese Debug-Kategorie existiert nicht:
diff --git a/translations/guhd-en_US.ts b/translations/guhd-en_US.ts
index 287005b4..863daa0c 100644
--- a/translations/guhd-en_US.ts
+++ b/translations/guhd-en_US.ts
@@ -4,7 +4,7 @@
main
-
+
guh ( /[guːh]/ ) is an open source IoT (Internet of Things) server,
which allows to control a lot of different devices from many different
@@ -23,12 +23,12 @@ for your environment.
-
+ Run guhd in the foreground, not as daemon.Run guhd in the foreground, not as daemon.
-
+ Debug categories to enable. Prefix with "No" to disable. Warnings from all categories will be printed unless explicitly muted with "NoWarnings".
Categories are:
@@ -37,17 +37,22 @@ Categories are:
Categories are:
-
+ Enables all debug categories. This parameter overrides all debug category parameters.Enables all debug categories. This parameter overrides all debug category parameters.
-
+ Specify a log file to write to, If this option is not specified, logs will be printed to the standard output.
-
+
+ If specified, all D-Bus interfaces will be bound to the session bus instead of the system bus.
+
+
+
+ No such debug category:No such debug category: