diff --git a/libnymea-app/configuration/nymeaconfiguration.cpp b/libnymea-app/configuration/nymeaconfiguration.cpp index 0122c35e..52932091 100644 --- a/libnymea-app/configuration/nymeaconfiguration.cpp +++ b/libnymea-app/configuration/nymeaconfiguration.cpp @@ -56,11 +56,20 @@ NymeaConfiguration::NymeaConfiguration(JsonRpcClient *client, QObject *parent): client->registerNotificationHandler(this, "Configuration", "notificationReceived"); } +bool NymeaConfiguration::fetchingData() const +{ + return m_fetchingData; +} + void NymeaConfiguration::init() { + m_fetchingData = true; + emit fetchingDataChanged(); + m_tcpServerConfigurations->clear(); m_webSocketServerConfigurations->clear(); m_mqttServerConfigurations->clear(); + m_tunnelProxyServerConfigurations->clear(); m_client->sendCommand("Configuration.GetConfigurations", this, "getConfigurationsResponse"); m_client->sendCommand("Configuration.GetMqttServerConfigurations", this, "getMqttServerConfigsReply"); m_client->sendCommand("Configuration.GetMqttPolicies", this, "getMqttPoliciesReply"); @@ -151,6 +160,11 @@ WebServerConfiguration *NymeaConfiguration::createWebServerConfiguration(const Q return ret; } +TunnelProxyServerConfiguration *NymeaConfiguration::createTunnelProxyServerConfiguration(const QString &address, int port, bool authEnabled, bool sslEnabled, bool ignoreSslErrors) +{ + return new TunnelProxyServerConfiguration(QUuid::createUuid().toString(), address, port, authEnabled, sslEnabled, ignoreSslErrors); +} + MqttPolicy *NymeaConfiguration::createMqttPolicy() const { return new MqttPolicy(QString(), QString(), QString(), {"#"}, {"#"}); @@ -318,6 +332,9 @@ void NymeaConfiguration::getConfigurationsResponse(int commandId, const QVariant TunnelProxyServerConfiguration *config = new TunnelProxyServerConfiguration(tunnelProxyServerConfigMap.value("id").toString(), tunnelProxyServerConfigMap.value("address").toString(), tunnelProxyServerConfigMap.value("port").toInt(), tunnelProxyServerConfigMap.value("authenticationEnabled").toBool(), tunnelProxyServerConfigMap.value("sslEnabled").toBool(), tunnelProxyServerConfigMap.value("ignoreSslErrors").toBool()); m_tunnelProxyServerConfigurations->addConfiguration(config); } + + m_fetchingData = false; + emit fetchingDataChanged(); } void NymeaConfiguration::setServerNameResponse(int commandId, const QVariantMap ¶ms) @@ -436,6 +453,7 @@ void NymeaConfiguration::deleteMqttPolicyReply(int commandId, const QVariantMap void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) { QString notif = notification.value("notification").toString(); + qWarning() << "Config notification received" << notif; if (notif == "Configuration.BasicConfigurationChanged") { QVariantMap params = notification.value("params").toMap().value("basicConfiguration").toMap(); m_debugServerEnabled = params.value("debugServerEnabled").toBool(); @@ -465,6 +483,10 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) configModel = m_webSocketServerConfigurations; params = notification.value("params").toMap().value("webSocketServerConfiguration").toMap(); } + if (notif == "Configuration.TunnelProxyServerConfigurationChanged") { + configModel = m_tunnelProxyServerConfigurations; + params = notification.value("params").toMap().value("tunnelProxyServerConfiguration").toMap(); + } if (notif == "Configuration.WebServerConfigurationChanged") { configModel = m_webServerConfigurations; params = notification.value("params").toMap().value("webServerConfiguration").toMap(); @@ -487,6 +509,8 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) if (!serverConfig) { if (notif == "Configuration.WebServerConfigurationChanged") { serverConfig = new WebServerConfiguration(params.value("id").toString()); + } else if (notif == "Configuration.TunnelProxyServerConfigurationChanged") { + serverConfig = new TunnelProxyServerConfiguration(params.value("id").toString()); } else { serverConfig = new ServerConfiguration(params.value("id").toString()); } @@ -510,6 +534,10 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) m_webSocketServerConfigurations->removeConfiguration(notification.value("params").toMap().value("id").toString()); return; } + if (notif == "Configuration.TunnelProxyServerConfigurationRemoved") { + m_tunnelProxyServerConfigurations->removeConfiguration(notification.value("params").toMap().value("id").toString()); + return; + } if (notif == "Configuration.WebServerConfigurationRemoved") { m_webServerConfigurations->removeConfiguration(notification.value("params").toMap().value("id").toString()); return; diff --git a/libnymea-app/configuration/nymeaconfiguration.h b/libnymea-app/configuration/nymeaconfiguration.h index 9a6ca1c2..8fc01a85 100644 --- a/libnymea-app/configuration/nymeaconfiguration.h +++ b/libnymea-app/configuration/nymeaconfiguration.h @@ -47,6 +47,8 @@ class NymeaConfiguration : public QObject { Q_OBJECT + Q_PROPERTY(bool fetchingData READ fetchingData NOTIFY fetchingDataChanged) + Q_PROPERTY(QString serverName READ serverName WRITE setServerName NOTIFY serverNameChanged) Q_PROPERTY(bool cloudEnabled READ cloudEnabled WRITE setCloudEnabled NOTIFY cloudEnabledChanged) @@ -63,6 +65,8 @@ class NymeaConfiguration : public QObject public: explicit NymeaConfiguration(JsonRpcClient* client, QObject *parent = nullptr); + bool fetchingData() const; + QString serverName() const; void setServerName(const QString &serverName); @@ -89,6 +93,7 @@ public: Q_INVOKABLE ServerConfiguration* createServerConfiguration(const QString &address = "0.0.0.0", int port = 0, bool authEnabled = false, bool sslEnabled = false); Q_INVOKABLE WebServerConfiguration* createWebServerConfiguration(const QString &address = "0.0.0.0", int port = 0, bool authEnabled = false, bool sslEnabled = false, const QString &publicFolder = QString()); + Q_INVOKABLE TunnelProxyServerConfiguration* createTunnelProxyServerConfiguration(const QString &address, int port, bool authEnabled = true, bool sslEnabled = true, bool ignoreSslErrors = false); Q_INVOKABLE MqttPolicy* createMqttPolicy() const; Q_INVOKABLE void setTcpServerConfiguration(ServerConfiguration *configuration); @@ -132,6 +137,7 @@ private: Q_INVOKABLE void notificationReceived(const QVariantMap ¬ification); signals: + void fetchingDataChanged(); void debugServerEnabledChanged(); void serverNameChanged(); void cloudEnabledChanged(); @@ -139,6 +145,7 @@ signals: private: JsonRpcClient* m_client = nullptr; + bool m_fetchingData = false; bool m_debugServerEnabled = false; QString m_serverName; bool m_cloudEnabled = false; diff --git a/libnymea-app/connection/discovery/nymeadiscovery.cpp b/libnymea-app/connection/discovery/nymeadiscovery.cpp index 32e0740f..677631b1 100644 --- a/libnymea-app/connection/discovery/nymeadiscovery.cpp +++ b/libnymea-app/connection/discovery/nymeadiscovery.cpp @@ -152,7 +152,6 @@ void NymeaDiscovery::cacheHost(NymeaHost *host) QList connections; Connection *remoteConnection = host->connections()->bestMatch(Connection::BearerTypeCloud); if (remoteConnection) { - qCritical() << "*********** caching" << remoteConnection->url(); connections.append(remoteConnection); } Connection *loopbackConnection = host->connections()->bestMatch(Connection::BearerTypeLoopback); diff --git a/libnymea-app/connection/nymeahost.cpp b/libnymea-app/connection/nymeahost.cpp index 578b8c71..d0b90a8d 100644 --- a/libnymea-app/connection/nymeahost.cpp +++ b/libnymea-app/connection/nymeahost.cpp @@ -247,6 +247,12 @@ Connection *Connections::bestMatch(Connection::BearerTypes bearerTypes) const return best; } +void Connections::addConnection(const QUrl &url, Connection::BearerType bearerType, bool secure, const QString &displayName) +{ + Connection *connection = new Connection(url, bearerType, secure, displayName); + addConnection(connection); +} + QHash Connections::roleNames() const { QHash roles; @@ -265,7 +271,7 @@ Connection::Connection(const QUrl &url, Connection::BearerType bearerType, bool m_secure(secure), m_displayName(displayName) { - + qRegisterMetaType("Connection.BearerType"); } Connection::~Connection() diff --git a/libnymea-app/connection/nymeahost.h b/libnymea-app/connection/nymeahost.h index c02c7b71..b1f25d9e 100644 --- a/libnymea-app/connection/nymeahost.h +++ b/libnymea-app/connection/nymeahost.h @@ -109,14 +109,16 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; - void addConnection(Connection *connection); - void removeConnection(Connection *connection); - void removeConnection(int index); Q_INVOKABLE Connection* find(const QUrl &url) const; Q_INVOKABLE Connection* get(int index) const; Q_INVOKABLE Connection* bestMatch(Connection::BearerTypes bearerTypes = Connection::BearerTypeAll) const; + void addConnection(Connection *connection); + Q_INVOKABLE void addConnection(const QUrl &url, Connection::BearerType bearerType, bool secure, const QString &displayName); + Q_INVOKABLE void removeConnection(Connection *connection); + Q_INVOKABLE void removeConnection(int index); + signals: void countChanged(); void connectionAdded(Connection *connection); diff --git a/libnymea-app/connection/tunnelproxytransport.cpp b/libnymea-app/connection/tunnelproxytransport.cpp index dcf50abf..4bda46fe 100644 --- a/libnymea-app/connection/tunnelproxytransport.cpp +++ b/libnymea-app/connection/tunnelproxytransport.cpp @@ -31,6 +31,7 @@ #include "tunnelproxytransport.h" #include +#include using namespace remoteproxyclient; @@ -58,14 +59,15 @@ TunnelProxyTransport::TunnelProxyTransport(QObject *parent) : bool TunnelProxyTransport::connect(const QUrl &url) { - m_url = url; + QUrl serverUrl; + serverUrl.setScheme(url.scheme() == "tunnels" ? "ssl" : "tcp"); + serverUrl.setHost(url.host()); + serverUrl.setPort(url.port()); + QUuid serverUuid = QUrlQuery(url).queryItemValue("uuid"); - QUrl serverUrl = QUrl("ssl://dev-remoteproxy.nymea.io:2213"); - QUuid serverUuid = url.host(); - - qCritical() << "Calling connect"; + qCritical() << "Calling connect on" << serverUrl << serverUuid; return m_remoteConnection->connectServer(serverUrl, serverUuid); } @@ -145,5 +147,5 @@ NymeaTransportInterface *TunnelProxyTransportFactory::createTransport(QObject *p QStringList TunnelProxyTransportFactory::supportedSchemes() const { - return { "tunnel" }; + return { "tunnel", "tunnels" }; } diff --git a/libnymea-app/jsonrpc/jsonrpcclient.cpp b/libnymea-app/jsonrpc/jsonrpcclient.cpp index c7b69095..c9187391 100644 --- a/libnymea-app/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app/jsonrpc/jsonrpcclient.cpp @@ -645,7 +645,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) JsonRpcReply *reply = m_replies.take(commandId); if (reply) { reply->deleteLater(); - qWarning() << QString("JsonRpc: got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented))) << reply->callback() << reply->callback(); +// qWarning() << QString("JsonRpc: got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented))) << reply->callback() << reply->callback(); if (dataMap.value("status").toString() == "unauthorized") { qWarning() << "Something's off with the token"; diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 74f77410..9efd9221 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -274,5 +274,6 @@ ui/components/NymeaToolTip.qml ui/components/SettingsTile.qml ui/components/NymeaTextField.qml + ui/system/TunnelProxyServerConfigurationDialog.qml diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index ab9bfcca..315aa3ae 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -312,7 +312,6 @@ Item { onConnectedChanged: { print("json client connected changed", engine.jsonRpcClient.connected, engine.jsonRpcClient.serverUuid) if (engine.jsonRpcClient.connected) { - engine.jsonRpcClient.currentHost.addTunnelConnection(); nymeaDiscovery.cacheHost(engine.jsonRpcClient.currentHost) configuredHost.uuid = engine.jsonRpcClient.serverUuid // tabSettings.lastConnectedHost = engine.jsonRpcClient.serverUuid @@ -345,6 +344,38 @@ Item { } } + Connections { + target: engine.nymeaConfiguration + onFetchingDataChanged: { + if (!engine.nymeaConfiguration.fetchingData) { + syncRemoteConnection() + } + } + } + Connections { + target: engien.nymeaConfiguration.tunnelProxyServerConfigurations + onCountChanged: syncRemoteConnection(); + } + + function syncRemoteConnection() { + for (var i = 0; i < engine.jsonRpcClient.currentHost.connections.count; i++) { + var connection = engine.jsonRpcClient.currentHost.connections.get(i) + if (connection.url.toString().startsWith("tunnel")) { + engine.jsonRpcClient.currentHost.connections.removeConnection(i--); + } + } + + for (var i = 0; i < engine.nymeaConfiguration.tunnelProxyServerConfigurations.count; i++) { + var tunnelProxyConfig = engine.nymeaConfiguration.tunnelProxyServerConfigurations.get(i); + var url = tunnelProxyConfig.sslEnabled ? "tunnels://" : "tunnel://"; + url += tunnelProxyConfig.address + url += ":" + tunnelProxyConfig.port + url += "?uuid=" + engine.jsonRpcClient.currentHost.uuid + engine.jsonRpcClient.currentHost.connections.addConnection(url, Connection.BearerTypeCloud, tunnelProxyConfig.sslEnabled, "Remote proxy connection"); + } + nymeaDiscovery.cacheHost(engine.jsonRpcClient.currentHost) + } + Connections { target: Qt.application enabled: engine.jsonRpcClient.connected && settings.returnToHome diff --git a/nymea-app/ui/system/ConnectionInterfacesPage.qml b/nymea-app/ui/system/ConnectionInterfacesPage.qml index 6d7e8101..db3c2c3f 100644 --- a/nymea-app/ui/system/ConnectionInterfacesPage.qml +++ b/nymea-app/ui/system/ConnectionInterfacesPage.qml @@ -150,7 +150,7 @@ SettingsPageBase { && engine.jsonRpcClient.currentConnection.port === model.port canDelete: !inUse onClicked: { - var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml")); + var component = Qt.createComponent(Qt.resolvedUrl("TunnelProxyServerConfigurationDialog.qml")); var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.tunnelProxyServerConfigurations.get(index).clone() }); popup.accepted.connect(function() { print("configuring:", popup.serverConfiguration.port) @@ -172,11 +172,11 @@ SettingsPageBase { Layout.margins: app.margins text: qsTr("Add") onClicked: { - var config = engine.nymeaConfiguration.createTunnelProxyServerConfiguration("0.0.0.0", 4444 + engine.nymeaConfiguration.webSocketServerConfigurations.count, false, false); - var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml")); + var config = engine.nymeaConfiguration.createTunnelProxyServerConfiguration("dev-remoteproxy.nymea.io", 2213, true, true, false); + var component = Qt.createComponent(Qt.resolvedUrl("TunnelProxyServerConfigurationDialog.qml")); var popup = component.createObject(root, { serverConfiguration: config }); popup.accepted.connect(function() { - engine.nymeaConfiguration.setWebSocketServerConfiguration(popup.serverConfiguration) + engine.nymeaConfiguration.setTunnelProxyServerConfiguration(popup.serverConfiguration) popup.serverConfiguration.destroy(); }) popup.rejected.connect(function() { diff --git a/nymea-app/ui/system/TunnelProxyServerConfigurationDialog.qml b/nymea-app/ui/system/TunnelProxyServerConfigurationDialog.qml new file mode 100644 index 00000000..2f0f053a --- /dev/null +++ b/nymea-app/ui/system/TunnelProxyServerConfigurationDialog.qml @@ -0,0 +1,114 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project 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 +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.9 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 + +Dialog { + id: root + title: qsTr("Proxy server configuration") + width: parent.width * .8 + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + + property ServerConfiguration serverConfiguration: null + standardButtons: Dialog.Ok | Dialog.Cancel + + ColumnLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + RowLayout { + Label { + text: qsTr("Proxy server address:") + Layout.fillWidth: true + } + TextField { + id: addressTextField + Layout.fillWidth: true + text: root.serverConfiguration ? root.serverConfiguration.address : "" + onEditingFinished: root.serverConfiguration.address = text + } + } + + RowLayout { + Label { + text: qsTr("Port:") + Layout.fillWidth: true + } + TextField { + inputMethodHints: Qt.ImhDigitsOnly + text: root.serverConfiguration ? root.serverConfiguration.port : 0 + validator: IntValidator { bottom: 0; top: 65535 } + onEditingFinished: root.serverConfiguration.port = text + } + } + + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Require login") + } + CheckBox { + checkState: root.serverConfiguration && root.serverConfiguration.authenticationEnabled ? Qt.Checked : Qt.Unchecked + onClicked: root.serverConfiguration.authenticationEnabled = checked + } + } + Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: qsTr("Not requiring a login for the remote connection will allow anyone on the internet to connect to your %1 system.").arg(Configuration.systemName) + color: Style.red + visible: root.serverConfiguration && !root.serverConfiguration.authenticationEnabled + } + + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("SSL enabled") + } + CheckBox { + checkState: root.serverConfiguration && root.serverConfiguration.sslEnabled ? Qt.Checked : Qt.Unchecked + onClicked: root.serverConfiguration.sslEnabled = checked + } + } + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Ignore SSL errors") + } + CheckBox { + checkState: root.serverConfiguration && root.serverConfiguration.ignoreSslErrors ? Qt.Checked : Qt.Unchecked + onClicked: root.serverConfiguration.ignoreSslErrors = checked + } + } + } +}