diff --git a/guh.pri b/guh.pri index c455026c..c0d4b503 100644 --- a/guh.pri +++ b/guh.pri @@ -5,7 +5,7 @@ GUH_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"') GUH_PLUGINS_PATH=/usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')/guh/plugins/ # define protocol versions -JSON_PROTOCOL_VERSION=49 +JSON_PROTOCOL_VERSION=50 REST_API_VERSION=1 DEFINES += GUH_VERSION_STRING=\\\"$${GUH_VERSION_STRING}\\\" \ diff --git a/server/bluetoothserver.cpp b/server/bluetoothserver.cpp index 1f215174..e1a5edb0 100644 --- a/server/bluetoothserver.cpp +++ b/server/bluetoothserver.cpp @@ -45,7 +45,7 @@ namespace guhserver { static const QBluetoothUuid serviceUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b")); /*! Constructs a \l{BluetoothServer} with the given \a parent. */ BluetoothServer::BluetoothServer(QObject *parent) : - TransportInterface(parent), + TransportInterface(ServerConfiguration(), parent), m_server(0) { diff --git a/server/guhconfiguration.cpp b/server/guhconfiguration.cpp index fff70b2b..05a2f051 100644 --- a/server/guhconfiguration.cpp +++ b/server/guhconfiguration.cpp @@ -48,7 +48,6 @@ GuhConfiguration::GuhConfiguration(QObject *parent) : if (settings.childGroups().contains("TcpServer")) { settings.beginGroup("TcpServer"); foreach (const QString &key, settings.childGroups()) { - qDebug() << "have key" << key; ServerConfiguration config = readServerConfig("TcpServer", key); m_tcpServerConfigs[config.id] = config; } @@ -70,16 +69,7 @@ GuhConfiguration::GuhConfiguration(QObject *parent) : if (settings.childGroups().contains("WebServer")) { settings.beginGroup("WebServer"); foreach (const QString &key, settings.childGroups()) { - ServerConfiguration tmp = readServerConfig("WebServer", key); - WebServerConfiguration config; - config.id = tmp.id; - config.address = tmp.address; - config.port = tmp.port; - config.sslEnabled = tmp.sslEnabled; - config.authenticationEnabled = tmp.authenticationEnabled; - settings.beginGroup(key); - config.publicFolder = settings.value("publicFolder").toString(); - settings.endGroup(); + WebServerConfiguration config = readWebServerConfig(key); m_webServerConfigs[config.id] = config; } settings.endGroup(); @@ -92,23 +82,21 @@ GuhConfiguration::GuhConfiguration(QObject *parent) : // TODO enable encryption/authentication by default once the important clients are supporting it config.sslEnabled = false; config.authenticationEnabled = false; - config.publicFolder = settings.value("publicFolder", "/usr/share/guh-webinterface/public/").toString(); + config.publicFolder = "/usr/share/guh-webinterface/public/"; #ifdef SNAPPY // Override default public folder path for snappy config.publicFolder = settings.value("publicFolder", QString(qgetenv("SNAP")) + "/guh-webinterface/").toString(); #endif m_webServerConfigs[config.id] = config; - storeServerConfig("WebServer", config); + storeWebServerConfig(config); } // WebSocket Server if (settings.childGroups().contains("WebSocketServer")) { settings.beginGroup("WebSocketServer"); foreach (const QString &key, settings.childGroups()) { - qWarning() << "have key" << key; ServerConfiguration config = readServerConfig("WebSocketServer", key); m_webSocketServerConfigs[config.id] = config; - qWarning() << "cound:" << m_webSocketServerConfigs.keys(); } settings.endGroup(); } else { @@ -234,6 +222,13 @@ void GuhConfiguration::setTcpServerConfiguration(const ServerConfiguration &conf emit tcpServerConfigurationChanged(config.id); } +void GuhConfiguration::removeTcpServerConfiguration(const QString &id) +{ + m_tcpServerConfigs.take(id); + deleteServerConfig("TcpServer", id); + emit tcpServerConfigurationRemoved(id); +} + QHash GuhConfiguration::webServerConfigurations() const { return m_webServerConfigs; @@ -256,6 +251,13 @@ void GuhConfiguration::setWebServerConfiguration(const WebServerConfiguration &c emit webServerConfigurationChanged(config.id); } +void GuhConfiguration::removeWebServerConfiguration(const QString &id) +{ + m_webServerConfigs.take(id); + deleteServerConfig("WebServer", id); + emit webServerConfigurationRemoved(id); +} + QHash GuhConfiguration::webSocketServerConfigurations() const { return m_webSocketServerConfigs; @@ -268,6 +270,13 @@ void GuhConfiguration::setWebSocketServerConfiguration(const ServerConfiguration emit webSocketServerConfigurationChanged(config.id); } +void GuhConfiguration::removeWebSocketServerConfiguration(const QString &id) +{ + m_webSocketServerConfigs.take(id); + deleteServerConfig("WebSocketServer", id); + emit webSocketServerConfigurationRemoved(id); +} + bool GuhConfiguration::bluetoothServerEnabled() const { return m_bluetoothServerEnabled; @@ -328,6 +337,7 @@ void GuhConfiguration::storeServerConfig(const QString &group, const ServerConfi { GuhSettings settings(GuhSettings::SettingsRoleGlobal); settings.beginGroup(group); + settings.remove("disabled"); settings.beginGroup(config.id); settings.setValue("address", config.address.toString()); settings.setValue("port", config.port); @@ -353,4 +363,43 @@ ServerConfiguration GuhConfiguration::readServerConfig(const QString &group, con return config; } +void GuhConfiguration::deleteServerConfig(const QString &group, const QString &id) +{ + GuhSettings settings(GuhSettings::SettingsRoleGlobal); + settings.beginGroup(group); + settings.remove(id); + if (settings.childGroups().isEmpty()) { + settings.setValue("disabled", true); + } + settings.endGroup(); +} + +void GuhConfiguration::storeWebServerConfig(const WebServerConfiguration &config) +{ + storeServerConfig("WebServer", config); + GuhSettings settings(GuhSettings::SettingsRoleGlobal); + settings.beginGroup("WebServer"); + settings.beginGroup(config.id); + settings.setValue("publicFolder", config.publicFolder); + settings.endGroup(); + settings.endGroup(); +} + +WebServerConfiguration GuhConfiguration::readWebServerConfig(const QString &id) +{ + WebServerConfiguration config; + GuhSettings settings(GuhSettings::SettingsRoleGlobal); + settings.beginGroup("WebServer"); + settings.beginGroup(id); + config.id = id; + config.address = QHostAddress(settings.value("address").toString()); + config.port = settings.value("port").toUInt(); + config.sslEnabled = settings.value("sslEnabled", true).toBool(); + config.authenticationEnabled = settings.value("authenticationEnabled", true).toBool(); + config.publicFolder = settings.value("publicFolder").toString(); + settings.endGroup(); + settings.endGroup(); + return config; +} + } diff --git a/server/guhconfiguration.h b/server/guhconfiguration.h index c72e61ba..358695d9 100644 --- a/server/guhconfiguration.h +++ b/server/guhconfiguration.h @@ -36,6 +36,14 @@ public: uint port = 0; bool sslEnabled = true; bool authenticationEnabled = true; + + bool operator==(const ServerConfiguration &other) { + return id == other.id + && address == other.address + && port == other.port + && sslEnabled == other.sslEnabled + && authenticationEnabled == other.authenticationEnabled; + } }; class WebServerConfiguration: public ServerConfiguration { @@ -53,6 +61,7 @@ public: ConfigurationErrorNoError, ConfigurationErrorInvalidTimeZone, ConfigurationErrorInvalidStationName, + ConfigurationErrorInvalidId, ConfigurationErrorInvalidPort, ConfigurationErrorInvalidHostAddress, ConfigurationErrorBluetoothHardwareNotAvailable, @@ -76,15 +85,17 @@ public: // TCP server QHash tcpServerConfigurations() const; void setTcpServerConfiguration(const ServerConfiguration &config); - void removeTcpServerConfiguration(const QUuid &id); + void removeTcpServerConfiguration(const QString &id); // Web server QHash webServerConfigurations() const; void setWebServerConfiguration(const WebServerConfiguration &config); + void removeWebServerConfiguration(const QString &id); // Websocket QHash webSocketServerConfigurations() const; void setWebSocketServerConfiguration(const ServerConfiguration &config); + void removeWebSocketServerConfiguration(const QString &id); // Bluetooth bool bluetoothServerEnabled() const; @@ -114,6 +125,9 @@ private: void storeServerConfig(const QString &group, const ServerConfiguration &config); ServerConfiguration readServerConfig(const QString &group, const QString &id); + void deleteServerConfig(const QString &group, const QString &id); + void storeWebServerConfig(const WebServerConfiguration &config); + WebServerConfiguration readWebServerConfig(const QString &id); signals: void serverNameChanged(); @@ -121,8 +135,11 @@ signals: void localeChanged(); void tcpServerConfigurationChanged(const QString &configId); + void tcpServerConfigurationRemoved(const QString &configId); void webServerConfigurationChanged(const QString &configId); + void webServerConfigurationRemoved(const QString &configId); void webSocketServerConfigurationChanged(const QString &configId); + void webSocketServerConfigurationRemoved(const QString &configId); void bluetoothServerEnabledChanged(); diff --git a/server/guhcore.cpp b/server/guhcore.cpp index fba15f36..13615e33 100644 --- a/server/guhcore.cpp +++ b/server/guhcore.cpp @@ -402,6 +402,10 @@ UserManager *GuhCore::userManager() const GuhCore::GuhCore(QObject *parent) : QObject(parent) { + staticMetaObject.invokeMethod(this, "init", Qt::QueuedConnection); +} + +void GuhCore::init() { qCDebug(dcApplication()) << "Loading guh configurations" << GuhSettings(GuhSettings::SettingsRoleGlobal).fileName(); m_configuration = new GuhConfiguration(this); @@ -447,6 +451,7 @@ GuhCore::GuhCore(QObject *parent) : connect(m_timeManager, &TimeManager::tick, m_deviceManager, &DeviceManager::timeTick); m_logger->logSystemEvent(m_timeManager->currentDateTime(), true); + emit initialized(); } /*! Connected to the DeviceManager's emitEvent signal. Events received in diff --git a/server/guhcore.h b/server/guhcore.h index 136a56ca..7bb60b22 100644 --- a/server/guhcore.h +++ b/server/guhcore.h @@ -81,6 +81,8 @@ public: static QStringList getAvailableLanguages(); signals: + void initialized(); + void pluginConfigChanged(const PluginId &id, const ParamList &config); void eventTriggered(const Event &event); void deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value); @@ -115,6 +117,7 @@ private: QHash m_pendingActions; private slots: + void init(); void gotEvent(const Event &event); void onDateTimeChanged(const QDateTime &dateTime); void onLocaleChanged(); diff --git a/server/jsonrpc/configurationhandler.cpp b/server/jsonrpc/configurationhandler.cpp index ddcbb2b7..428c553a 100644 --- a/server/jsonrpc/configurationhandler.cpp +++ b/server/jsonrpc/configurationhandler.cpp @@ -90,21 +90,15 @@ ConfigurationHandler::ConfigurationHandler(QObject *parent): basicConfiguration.insert("timeZone", JsonTypes::basicTypeToString(JsonTypes::String)); basicConfiguration.insert("language", JsonTypes::basicTypeToString(JsonTypes::String)); returns.insert("basicConfiguration", basicConfiguration); - QVariantMap tcpServerConfiguration; - tcpServerConfiguration.insert("host", JsonTypes::basicTypeToString(JsonTypes::String)); - tcpServerConfiguration.insert("port", JsonTypes::basicTypeToString(JsonTypes::Uint)); - returns.insert("tcpServerConfiguration", tcpServerConfiguration); - QVariantMap webServerConfiguration; - webServerConfiguration.insert("host", JsonTypes::basicTypeToString(JsonTypes::String)); - webServerConfiguration.insert("port", JsonTypes::basicTypeToString(JsonTypes::Uint)); - returns.insert("webServerConfiguration", webServerConfiguration); - QVariantMap webSocketServerConfiguration; - webSocketServerConfiguration.insert("host", JsonTypes::basicTypeToString(JsonTypes::String)); - webSocketServerConfiguration.insert("port", JsonTypes::basicTypeToString(JsonTypes::Uint)); - returns.insert("webSocketServerConfiguration", webSocketServerConfiguration); - QVariantMap sslConfiguration; - sslConfiguration.insert("enabled", JsonTypes::basicTypeToString(JsonTypes::Bool)); - returns.insert("sslConfiguration", sslConfiguration); + QVariantList tcpServerConfigurations; + tcpServerConfigurations.append(JsonTypes::serverConfigurationRef()); + returns.insert("tcpServerConfigurations", tcpServerConfigurations); + QVariantList webServerConfigurations; + webServerConfigurations.append(JsonTypes::webServerConfigurationRef()); + returns.insert("webServerConfigurations", webServerConfigurations); + QVariantList webSocketServerConfigurations; + webSocketServerConfigurations.append(JsonTypes::serverConfigurationRef()); + returns.insert("webSocketServerConfigurations", webSocketServerConfigurations); setReturns("GetConfigurations", returns); params.clear(); returns.clear(); @@ -129,29 +123,47 @@ ConfigurationHandler::ConfigurationHandler(QObject *parent): setReturns("SetLanguage", returns); params.clear(); returns.clear(); - setDescription("SetTcpServerConfiguration", "Configure the TCP interface of the server. Note: if you are using the TCP server for this call you will loose the connection."); - params.insert("host", JsonTypes::basicTypeToString(JsonTypes::String)); - params.insert("port", JsonTypes::basicTypeToString(JsonTypes::Uint)); + setDescription("SetTcpServerConfiguration", "Configure a TCP interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Note: if you are changing the configuration for the interface you are currently connected to, the connection will be dropped."); + params.insert("configuration", JsonTypes::serverConfigurationRef()); setParams("SetTcpServerConfiguration", params); returns.insert("configurationError", JsonTypes::configurationErrorRef()); setReturns("SetTcpServerConfiguration", returns); params.clear(); returns.clear(); - setDescription("SetWebSocketServerConfiguration", "Configure the web socket interface of the server. Note: if you are using the web socket server for this call you will loose the connection."); - params.insert("host", JsonTypes::basicTypeToString(JsonTypes::String)); - params.insert("port", JsonTypes::basicTypeToString(JsonTypes::Uint)); + setDescription("DeleteTcpServerConfiguration", "Delete a TCP interface of the server. Note: if you are deleting the configuration for the interface you are currently connected to, the connection will be dropped."); + params.insert("id", JsonTypes::basicTypeToString(QVariant::String)); + setParams("DeleteTcpServerConfiguration", params); + returns.insert("configurationError", JsonTypes::configurationErrorRef()); + setReturns("DeleteTcpServerConfiguration", returns); + + params.clear(); returns.clear(); + setDescription("SetWebSocketServerConfiguration", "Configure a WebSocket Server interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Note: if you are changing the configuration for the interface you are currently connected to, the connection will be dropped."); + params.insert("configuration", JsonTypes::serverConfigurationRef()); setParams("SetWebSocketServerConfiguration", params); returns.insert("configurationError", JsonTypes::configurationErrorRef()); setReturns("SetWebSocketServerConfiguration", returns); params.clear(); returns.clear(); - setDescription("SetWebServerConfiguration", "Configure the web server of the server."); - params.insert("host", JsonTypes::basicTypeToString(JsonTypes::String)); - params.insert("port", JsonTypes::basicTypeToString(JsonTypes::Uint)); + setDescription("DeleteWebSocketServerConfiguration", "Delete a WebSocket Server interface of the server. Note: if you are deleting the configuration for the interface you are currently connected to, the connection will be dropped."); + params.insert("id", JsonTypes::basicTypeToString(QVariant::String)); + setParams("DeleteWebSocketServerConfiguration", params); + returns.insert("configurationError", JsonTypes::configurationErrorRef()); + setReturns("DeleteWebSocketServerConfiguration", returns); + + params.clear(); returns.clear(); + setDescription("SetWebServerConfiguration", "Configure a WebServer interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added."); + params.insert("configuration", JsonTypes::webServerConfigurationRef()); setParams("SetWebServerConfiguration", params); returns.insert("configurationError", JsonTypes::configurationErrorRef()); setReturns("SetWebServerConfiguration", returns); + params.clear(); returns.clear(); + setDescription("DeleteWebServerConfiguration", "Delete a WebServer interface of the server."); + params.insert("id", JsonTypes::basicTypeToString(QVariant::String)); + setParams("DeleteWebServerConfiguration", params); + returns.insert("configurationError", JsonTypes::configurationErrorRef()); + setReturns("DeleteWebServerConfiguration", returns); + // Notifications params.clear(); returns.clear(); setDescription("BasicConfigurationChanged", "Emitted whenever the basic configuration of this server changes."); @@ -215,13 +227,13 @@ JsonReply *ConfigurationHandler::GetConfigurations(const QVariantMap ¶ms) co webServerConfigs.append(JsonTypes::packWebServerConfiguration(config)); } - returns.insert("webServerConfigurations", tcpServerConfigs); + returns.insert("webServerConfigurations", webServerConfigs); QVariantList webSocketServerConfigs; foreach (const ServerConfiguration &config, GuhCore::instance()->configuration()->webSocketServerConfigurations()) { - tcpServerConfigs.append(JsonTypes::packServerConfiguration(config)); + webSocketServerConfigs.append(JsonTypes::packServerConfiguration(config)); } - returns.insert("webServerConfigurations", tcpServerConfigs); + returns.insert("webSocketServerConfigurations", webSocketServerConfigs); return createReply(returns); } @@ -282,7 +294,10 @@ JsonReply *ConfigurationHandler::SetLanguage(const QVariantMap ¶ms) const JsonReply *ConfigurationHandler::SetTcpServerConfiguration(const QVariantMap ¶ms) const { - ServerConfiguration config = JsonTypes::unpackServerConfiguration(params); + ServerConfiguration config = JsonTypes::unpackServerConfiguration(params.value("configuration").toMap()); + if (config.id.isEmpty()) { + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidId)); + } if (config.address.isNull()) return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidHostAddress)); @@ -300,10 +315,24 @@ JsonReply *ConfigurationHandler::SetTcpServerConfiguration(const QVariantMap &pa return createReply(statusToReply(GuhConfiguration::ConfigurationErrorNoError)); } +JsonReply *ConfigurationHandler::DeleteTcpServerConfiguration(const QVariantMap ¶ms) const +{ + QString id = params.value("id").toString(); + if (id.isEmpty() || !GuhCore::instance()->configuration()->tcpServerConfigurations().contains(id)) { + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidId)); + } + GuhCore::instance()->configuration()->removeTcpServerConfiguration(id); + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorNoError)); +} + JsonReply *ConfigurationHandler::SetWebServerConfiguration(const QVariantMap ¶ms) const { - WebServerConfiguration config = JsonTypes::unpackWebServerConfiguration(params); + qWarning() << params; + WebServerConfiguration config = JsonTypes::unpackWebServerConfiguration(params.value("configuration").toMap()); + if (config.id.isEmpty()) { + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidId)); + } if (config.address.isNull()) return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidHostAddress)); @@ -320,9 +349,22 @@ JsonReply *ConfigurationHandler::SetWebServerConfiguration(const QVariantMap &pa return createReply(statusToReply(GuhConfiguration::ConfigurationErrorNoError)); } +JsonReply *ConfigurationHandler::DeleteWebServerConfiguration(const QVariantMap ¶ms) const +{ + QString id = params.value("id").toString(); + if (id.isEmpty() || !GuhCore::instance()->configuration()->webServerConfigurations().contains(id)) { + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidId)); + } + GuhCore::instance()->configuration()->removeWebServerConfiguration(id); + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorNoError)); +} + JsonReply *ConfigurationHandler::SetWebSocketServerConfiguration(const QVariantMap ¶ms) const { - ServerConfiguration config = JsonTypes::unpackServerConfiguration(params); + ServerConfiguration config = JsonTypes::unpackServerConfiguration(params.value("configuration").toMap()); + if (config.id.isEmpty()) { + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidId)); + } if (config.address.isNull()) return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidHostAddress)); @@ -334,11 +376,20 @@ JsonReply *ConfigurationHandler::SetWebSocketServerConfiguration(const QVariantM qCDebug(dcJsonRpc()) << QString("Configure web socket server %1:%2").arg(config.address.toString()).arg(config.port); GuhCore::instance()->configuration()->setWebSocketServerConfiguration(config); -// GuhCore::instance()->webSocketServer()->reconfigureServer(config); return createReply(statusToReply(GuhConfiguration::ConfigurationErrorNoError)); } +JsonReply *ConfigurationHandler::DeleteWebSocketServerConfiguration(const QVariantMap ¶ms) const +{ + QString id = params.value("id").toString(); + if (id.isEmpty() || !GuhCore::instance()->configuration()->webSocketServerConfigurations().contains(id)) { + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorInvalidId)); + } + GuhCore::instance()->configuration()->removeWebSocketServerConfiguration(id); + return createReply(statusToReply(GuhConfiguration::ConfigurationErrorNoError)); +} + void ConfigurationHandler::onBasicConfigurationChanged() { QVariantMap params; diff --git a/server/jsonrpc/configurationhandler.h b/server/jsonrpc/configurationhandler.h index 5d2afabe..9c0f1a78 100644 --- a/server/jsonrpc/configurationhandler.h +++ b/server/jsonrpc/configurationhandler.h @@ -42,8 +42,11 @@ public: Q_INVOKABLE JsonReply *SetTimeZone(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *SetLanguage(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *SetTcpServerConfiguration(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *DeleteTcpServerConfiguration(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *SetWebServerConfiguration(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *DeleteWebServerConfiguration(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *SetWebSocketServerConfiguration(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *DeleteWebSocketServerConfiguration(const QVariantMap ¶ms) const; signals: void BasicConfigurationChanged(const QVariantMap ¶ms); diff --git a/server/jsonrpc/jsonrpcserver.cpp b/server/jsonrpc/jsonrpcserver.cpp index aea6d594..13723835 100644 --- a/server/jsonrpc/jsonrpcserver.cpp +++ b/server/jsonrpc/jsonrpcserver.cpp @@ -237,18 +237,23 @@ QHash JsonRPCServer::handlers() const return m_handlers; } -/*! Register a new \l{TransportInterface} to the JSON server. The \a enabled flag indivates if the given \a interface sould be enebeld on startup. */ -void JsonRPCServer::registerTransportInterface(TransportInterface *interface, bool enabled, bool authenticationRequired) +/*! Register a new \l{TransportInterface} to the JSON server. If the given interface is already registered, just the authenticationRequired flag will be updated. */ +void JsonRPCServer::registerTransportInterface(TransportInterface *interface, bool authenticationRequired) { - connect(interface, &TransportInterface::clientConnected, this, &JsonRPCServer::clientConnected); - connect(interface, &TransportInterface::clientDisconnected, this, &JsonRPCServer::clientDisconnected); - connect(interface, &TransportInterface::dataAvailable, this, &JsonRPCServer::processData); - - m_interfaces.insert(interface, authenticationRequired); - - if (enabled) { - QMetaObject::invokeMethod(interface, "startServer", Qt::QueuedConnection); + if (!m_interfaces.contains(interface)) { + connect(interface, &TransportInterface::clientConnected, this, &JsonRPCServer::clientConnected); + connect(interface, &TransportInterface::clientDisconnected, this, &JsonRPCServer::clientDisconnected); + connect(interface, &TransportInterface::dataAvailable, this, &JsonRPCServer::processData); } + m_interfaces[interface] = authenticationRequired; +} + +void JsonRPCServer::unregisterTransportInterface(TransportInterface *interface) +{ + disconnect(interface, &TransportInterface::clientConnected, this, &JsonRPCServer::clientConnected); + disconnect(interface, &TransportInterface::clientDisconnected, this, &JsonRPCServer::clientDisconnected); + disconnect(interface, &TransportInterface::dataAvailable, this, &JsonRPCServer::processData); + m_interfaces.take(interface); } /*! Send a JSON success response to the client with the given \a clientId, @@ -454,7 +459,8 @@ void JsonRPCServer::clientConnected(const QUuid &clientId) handshake.insert("uuid", GuhCore::instance()->configuration()->serverUuid().toString()); handshake.insert("language", GuhCore::instance()->configuration()->locale().name()); handshake.insert("protocol version", JSON_PROTOCOL_VERSION); - handshake.insert("initialSetupRequired", GuhCore::instance()->userManager()->users().isEmpty()); + handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? GuhCore::instance()->userManager()->users().isEmpty() : false)); + handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled); interface->sendData(clientId, QJsonDocument::fromVariant(handshake).toJson()); } diff --git a/server/jsonrpc/jsonrpcserver.h b/server/jsonrpc/jsonrpcserver.h index 574d9671..3eb6faef 100644 --- a/server/jsonrpc/jsonrpcserver.h +++ b/server/jsonrpc/jsonrpcserver.h @@ -58,7 +58,8 @@ public: QHash handlers() const; - void registerTransportInterface(TransportInterface *interface, bool enabled, bool authenticationRequired); + void registerTransportInterface(TransportInterface *interface, bool authenticationRequired); + void unregisterTransportInterface(TransportInterface *interface); private: void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap()); diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index af228d4e..2ccca746 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -119,6 +119,8 @@ QVariantMap JsonTypes::s_wirelessAccessPoint; QVariantMap JsonTypes::s_wiredNetworkDevice; QVariantMap JsonTypes::s_wirelessNetworkDevice; QVariantMap JsonTypes::s_tokenInfo; +QVariantMap JsonTypes::s_serverConfiguration; +QVariantMap JsonTypes::s_webServerConfiguration; void JsonTypes::init() { @@ -359,6 +361,16 @@ void JsonTypes::init() s_tokenInfo.insert("deviceName", basicTypeToString(QVariant::String)); s_tokenInfo.insert("creationTime", basicTypeToString(QVariant::UInt)); + // ServerConfiguration + s_serverConfiguration.insert("id", basicTypeToString(QVariant::String)); + s_serverConfiguration.insert("address", basicTypeToString(QVariant::String)); + s_serverConfiguration.insert("port", basicTypeToString(QVariant::UInt)); + s_serverConfiguration.insert("sslEnabled", basicTypeToString(QVariant::Bool)); + s_serverConfiguration.insert("authenticationEnabled", basicTypeToString(QVariant::Bool)); + + s_webServerConfiguration = s_serverConfiguration; + s_webServerConfiguration.insert("publicFolder", basicTypeToString(QVariant::String)); + s_initialized = true; } @@ -437,6 +449,7 @@ QVariantMap JsonTypes::allTypes() allTypes.insert("WiredNetworkDevice", wiredNetworkDeviceDescription()); allTypes.insert("WirelessNetworkDevice", wirelessNetworkDeviceDescription()); allTypes.insert("TokenInfo", tokenInfoDescription()); + allTypes.insert("ServerConfiguration", serverConfigurationDescription()); return allTypes; } @@ -1091,7 +1104,8 @@ QVariantMap JsonTypes::packBasicConfiguration() QVariantMap JsonTypes::packServerConfiguration(const ServerConfiguration &config) { QVariantMap serverConfiguration; - serverConfiguration.insert("host", config.address.toString()); + serverConfiguration.insert("id", config.id); + serverConfiguration.insert("address", config.address.toString()); serverConfiguration.insert("port", config.port); serverConfiguration.insert("sslEnabled", config.sslEnabled); serverConfiguration.insert("authenticationEnabled", config.authenticationEnabled); @@ -1834,6 +1848,18 @@ QPair JsonTypes::validateVariant(const QVariant &templateVariant, qCWarning(dcJsonRpc) << "TokenInfo not matching"; return result; } + } else if (refName == serverConfigurationRef()) { + QPair result = validateMap(serverConfigurationDescription(), variant.toMap()); + if (!result.first) { + qCWarning(dcJsonRpc) << "ServerConfiguration not matching"; + return result; + } + } else if (refName == webServerConfigurationRef()) { + QPair result = validateMap(webServerConfigurationDescription(), variant.toMap()); + if (!result.first) { + qCWarning(dcJsonRpc) << "WebServerConfiguration not matching"; + return result; + } } else if (refName == basicTypeRef()) { QPair result = validateBasicType(variant); if (!result.first) { diff --git a/server/jsonrpc/jsontypes.h b/server/jsonrpc/jsontypes.h index 63413e89..839bccba 100644 --- a/server/jsonrpc/jsontypes.h +++ b/server/jsonrpc/jsontypes.h @@ -166,6 +166,8 @@ public: DECLARE_OBJECT(wiredNetworkDevice, "WiredNetworkDevice") DECLARE_OBJECT(wirelessNetworkDevice, "WirelessNetworkDevice") DECLARE_OBJECT(tokenInfo, "TokenInfo") + DECLARE_OBJECT(serverConfiguration, "ServerConfiguration") + DECLARE_OBJECT(webServerConfiguration, "WebServerConfiguration") // pack types static QVariantMap packEventType(const EventType &eventType); diff --git a/server/servermanager.cpp b/server/servermanager.cpp index 0e566adb..a4f556be 100644 --- a/server/servermanager.cpp +++ b/server/servermanager.cpp @@ -94,27 +94,43 @@ ServerManager::ServerManager(GuhConfiguration* configuration, QObject *parent) : // Transports #ifdef TESTING_ENABLED MockTcpServer *tcpServer = new MockTcpServer(this); - m_jsonServer->registerTransportInterface(tcpServer, true, true); + m_jsonServer->registerTransportInterface(tcpServer, true); + tcpServer->startServer(); #else foreach (const ServerConfiguration &config, configuration->tcpServerConfigurations()) { - TcpServer *tcpServer = new TcpServer(config.address, config.port, config.sslEnabled, m_sslConfiguration, this); - m_jsonServer->registerTransportInterface(tcpServer, true, config.authenticationEnabled); + TcpServer *tcpServer = new TcpServer(config, m_sslConfiguration, this); + m_jsonServer->registerTransportInterface(tcpServer, config.authenticationEnabled); + m_tcpServers.insert(config.id, tcpServer); + tcpServer->startServer(); } #endif foreach (const ServerConfiguration &config, configuration->webSocketServerConfigurations()) { qWarning() << "Have websockeserver config" << config.id; - WebSocketServer *webSocketServer = new WebSocketServer(config.address, config.port, config.sslEnabled, m_sslConfiguration, this); - m_jsonServer->registerTransportInterface(webSocketServer, true, config.authenticationEnabled); + WebSocketServer *webSocketServer = new WebSocketServer(config, m_sslConfiguration, this); + m_jsonServer->registerTransportInterface(webSocketServer, config.authenticationEnabled); + m_webSocketServers.insert(config.id, webSocketServer); + webSocketServer->startServer(); } m_bluetoothServer = new BluetoothServer(this); - m_jsonServer->registerTransportInterface(m_bluetoothServer, configuration->bluetoothServerEnabled(), true); + m_jsonServer->registerTransportInterface(m_bluetoothServer, true); + if (configuration->bluetoothServerEnabled()) { + m_bluetoothServer->startServer(); + } foreach (const WebServerConfiguration &config, configuration->webServerConfigurations()) { - WebServer *webServer = new WebServer(config.address, config.port, config.publicFolder, config.sslEnabled, m_sslConfiguration, this); + WebServer *webServer = new WebServer(config, m_sslConfiguration, this); m_restServer->registerWebserver(webServer); + m_webServers.insert(config.id, webServer); } + + connect(configuration, &GuhConfiguration::tcpServerConfigurationChanged, this, &ServerManager::tcpServerConfigurationChanged); + connect(configuration, &GuhConfiguration::tcpServerConfigurationRemoved, this, &ServerManager::tcpServerConfigurationRemoved); + connect(configuration, &GuhConfiguration::webSocketServerConfigurationChanged, this, &ServerManager::webSocketServerConfigurationChanged); + connect(configuration, &GuhConfiguration::webSocketServerConfigurationRemoved, this, &ServerManager::webSocketServerConfigurationRemoved); + connect(configuration, &GuhConfiguration::webServerConfigurationChanged, this, &ServerManager::webServerConfigurationChanged); + connect(configuration, &GuhConfiguration::webServerConfigurationRemoved, this, &ServerManager::webServerConfigurationRemoved); } /*! Returns the pointer to the created \l{JsonRPCServer} in this \l{ServerManager}. */ @@ -134,6 +150,99 @@ BluetoothServer *ServerManager::bluetoothServer() const return m_bluetoothServer; } +void ServerManager::tcpServerConfigurationChanged(const QString &id) +{ +#ifndef TESTING_ENABLED + ServerConfiguration config = GuhCore::instance()->configuration()->tcpServerConfigurations().value(id); + TcpServer *server = m_tcpServers.value(id); + if (server) { + qDebug(dcConnection) << "Restarting TCP server for" << config.address << config.port << "SSL" << (config.sslEnabled ? "enabled" : "disabled") << "Authentication" << (config.authenticationEnabled ? "enabled" : "disabled"); + server->stopServer(); + server->setConfiguration(config); + } else { + qDebug(dcConnection) << "Received a TCP Server config change event but don't have a TCP Server instance for it. Creating new Server instance."; + server = new TcpServer(config, m_sslConfiguration, this); + m_tcpServers.insert(config.id, server); + } + m_jsonServer->registerTransportInterface(server, config.authenticationEnabled); + server->startServer(); +#else + qWarning() << "Configure called for" << id << "but disabled in testing"; +#endif +} + +void ServerManager::tcpServerConfigurationRemoved(const QString &id) +{ +#ifndef TESTING_ENABLED + if (!m_tcpServers.contains(id)) { + qWarning(dcConnection) << "Received a TCP Server config removed event but don't have a TCP Server instance for it."; + return; + } + TcpServer *server = m_tcpServers.take(id); + m_jsonServer->unregisterTransportInterface(server); + server->stopServer(); + server->deleteLater(); +#else + qWarning() << "Delete configuration called for" << id << "but disabled in testing"; +#endif +} + +void ServerManager::webSocketServerConfigurationChanged(const QString &id) +{ + WebSocketServer *server = m_webSocketServers.value(id); + ServerConfiguration config = GuhCore::instance()->configuration()->webSocketServerConfigurations().value(id); + if (server) { + qDebug(dcConnection) << "Restarting WebSocket server for" << config.address << config.port << "SSL" << (config.sslEnabled ? "enabled" : "disabled") << "Authentication" << (config.authenticationEnabled ? "enabled" : "disabled"); + server->stopServer(); + server->setConfiguration(config); + } else { + qDebug(dcConnection) << "Received a WebSocket Server config change event but don't have a WebSocket Server instance for it. Creating new instance."; + server = new WebSocketServer(config, m_sslConfiguration, this); + m_webSocketServers.insert(server->configuration().id, server); + } + m_jsonServer->registerTransportInterface(server, config.authenticationEnabled); + server->startServer(); +} + +void ServerManager::webSocketServerConfigurationRemoved(const QString &id) +{ + if (!m_webSocketServers.contains(id)) { + qWarning(dcConnection) << "Received a WebSocket Server config removed event but don't have a WebSocket Server instance for it."; + return; + } + WebSocketServer *server = m_webSocketServers.take(id); + m_jsonServer->unregisterTransportInterface(server); + server->stopServer(); + server->deleteLater(); +} + +void ServerManager::webServerConfigurationChanged(const QString &id) +{ + WebServerConfiguration config = GuhCore::instance()->configuration()->webServerConfigurations().value(id); + WebServer *server = m_webServers.value(id); + if (server) { + qDebug(dcConnection) << "Restarting Web server for" << config.address << config.port << "SSL" << (config.sslEnabled ? "enabled" : "disabled") << "Authentication" << (config.authenticationEnabled ? "enabled" : "disabled"); + server->stopServer(); + server->reconfigureServer(config); + } else { + qDebug(dcConnection) << "Received a Web Server config change event but don't have a Web Server instance for it."; + server = new WebServer(config, m_sslConfiguration, this); + m_webServers.insert(config.id, server); + } + server->startServer(); +} + +void ServerManager::webServerConfigurationRemoved(const QString &id) +{ + if (!m_webServers.contains(id)) { + qWarning(dcConnection) << "Received a Web Server config removed event but don't have a Web Server instance for it."; + return; + } + WebServer *server = m_webServers.take(id); + server->stopServer(); + server->deleteLater(); +} + bool ServerManager::loadCertificate(const QString &certificateKeyFileName, const QString &certificateFileName) { QFile certificateKeyFile(certificateKeyFileName); diff --git a/server/servermanager.h b/server/servermanager.h index 6b51cfb7..f415c5c5 100644 --- a/server/servermanager.h +++ b/server/servermanager.h @@ -62,12 +62,25 @@ public: TcpServer *tcpServer() const; #endif +private slots: + void tcpServerConfigurationChanged(const QString &id); + void tcpServerConfigurationRemoved(const QString &id); + void webSocketServerConfigurationChanged(const QString &id); + void webSocketServerConfigurationRemoved(const QString &id); + void webServerConfigurationChanged(const QString &id); + void webServerConfigurationRemoved(const QString &id); + private: // Interfaces JsonRPCServer *m_jsonServer; RestServer *m_restServer; BluetoothServer *m_bluetoothServer; +#ifndef TESTING_ENABLED + QHash m_tcpServers; +#endif + QHash m_webSocketServers; + QHash m_webServers; // Encrytption and stuff QSslConfiguration m_sslConfiguration; diff --git a/server/tcpserver.cpp b/server/tcpserver.cpp index 32c9d428..e087ab32 100644 --- a/server/tcpserver.cpp +++ b/server/tcpserver.cpp @@ -45,14 +45,12 @@ namespace guhserver { * * \sa ServerManager */ -TcpServer::TcpServer(const QHostAddress &host, const uint &port, bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) : - TransportInterface(parent), +TcpServer::TcpServer(const ServerConfiguration &configuration, const QSslConfiguration &sslConfiguration, QObject *parent) : + TransportInterface(configuration, parent), m_server(NULL), - m_host(host), - m_port(port), - m_sslEnabled(sslEnabled), m_sslConfig(sslConfiguration) { + qWarning() << "****" << configuration.address << configuration.port; #ifndef TESTING_ENABLED m_avahiService = new QtAvahiService(this); connect(m_avahiService, &QtAvahiService::serviceStateChanged, this, &TcpServer::onAvahiServiceStateChanged); @@ -128,30 +126,18 @@ void TcpServer::onAvahiServiceStateChanged(const QtAvahiService::QtAvahiServiceS /*! Returns true if this \l{TcpServer} could be reconfigured with the given \a address and \a port. */ -bool TcpServer::reconfigureServer(const QHostAddress &address, const uint &port) +void TcpServer::reconfigureServer(const ServerConfiguration &config) { - if (m_host == address && m_port == (qint16)port && m_server->isListening()) - return true; + if (configuration().address == config.address && + configuration().port == config.port && + configuration().sslEnabled == config.sslEnabled && + configuration().authenticationEnabled == config.authenticationEnabled && + m_server->isListening()) + return; stopServer(); - - SslServer *server = new SslServer(m_sslEnabled, m_sslConfig); - if(!server->listen(address, port)) { - qCWarning(dcConnection) << "Tcp server error: can not listen on" << address.toString() << port; - delete server; - // Restart the server with the old configuration - qCDebug(dcTcpServer()) << "Restart server with old configuration."; - startServer(); - return false; - } - // Remove the test server.. - server->close(); - delete server; - - // Start server with new configuration - m_host = address; - m_port = port; - return startServer(); + setConfiguration(config); + startServer(); } /*! Returns true if this \l{TcpServer} started successfully. @@ -160,9 +146,9 @@ bool TcpServer::reconfigureServer(const QHostAddress &address, const uint &port) */ bool TcpServer::startServer() { - m_server = new SslServer(m_sslEnabled, m_sslConfig); - if(!m_server->listen(m_host, m_port)) { - qCWarning(dcConnection) << "Tcp server error: can not listen on" << m_host.toString() << m_port; + m_server = new SslServer(configuration().sslEnabled, m_sslConfig); + if(!m_server->listen(configuration().address, configuration().port)) { + qCWarning(dcConnection) << "Tcp server error: can not listen on" << configuration().address.toString() << configuration().port; delete m_server; m_server = NULL; return false; @@ -177,7 +163,7 @@ bool TcpServer::startServer() txt.insert("manufacturer", "guh GmbH"); txt.insert("uuid", GuhCore::instance()->configuration()->serverUuid().toString()); txt.insert("name", GuhCore::instance()->configuration()->serverName()); - m_avahiService->registerService("guhIO", m_port, "_jsonrpc._tcp", txt); + m_avahiService->registerService("guhIO", configuration().port, "_jsonrpc._tcp", txt); #endif qCDebug(dcConnection) << "Started Tcp server on" << m_server->serverAddress().toString() << m_server->serverPort(); diff --git a/server/tcpserver.h b/server/tcpserver.h index f1cdbd3b..db508f78 100644 --- a/server/tcpserver.h +++ b/server/tcpserver.h @@ -71,7 +71,7 @@ class TcpServer : public TransportInterface { Q_OBJECT public: - explicit TcpServer(const QHostAddress &host, const uint &port, bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = 0); + explicit TcpServer(const ServerConfiguration &configuration, const QSslConfiguration &sslConfiguration, QObject *parent = 0); ~TcpServer(); void sendData(const QUuid &clientId, const QByteArray &data) override; @@ -85,10 +85,6 @@ private: SslServer * m_server; QHash m_clientList; - QHostAddress m_host; - qint16 m_port; - - bool m_sslEnabled = false; QSslConfiguration m_sslConfig; private slots: @@ -102,7 +98,7 @@ private slots: public slots: - bool reconfigureServer(const QHostAddress &address, const uint &port); + void reconfigureServer(const ServerConfiguration &configuration); bool startServer() override; bool stopServer() override; }; diff --git a/server/transportinterface.cpp b/server/transportinterface.cpp index d16fb289..50ba82ea 100644 --- a/server/transportinterface.cpp +++ b/server/transportinterface.cpp @@ -78,12 +78,24 @@ namespace guhserver { /*! Constructs a \l{TransportInterface} with the given \a parent. */ -TransportInterface::TransportInterface(QObject *parent) : - QObject(parent) +TransportInterface::TransportInterface(const ServerConfiguration &config, QObject *parent) : + QObject(parent), + m_config(config) { } -/*! Pure virtual destructor for \l{TransportInterface}. */ +void TransportInterface::setConfiguration(const ServerConfiguration &config) +{ + m_config = config; +} + +/*! Returns the \{ServerConfiguration}. */ +ServerConfiguration TransportInterface::configuration() const +{ + return m_config; +} + +/*! Virtual destructor for \l{TransportInterface}. */ TransportInterface::~TransportInterface() { } diff --git a/server/transportinterface.h b/server/transportinterface.h index e3ada702..5a012f2b 100644 --- a/server/transportinterface.h +++ b/server/transportinterface.h @@ -26,18 +26,23 @@ #include #include +#include "guhconfiguration.h" + namespace guhserver { class TransportInterface : public QObject { Q_OBJECT public: - explicit TransportInterface(QObject *parent = 0); + explicit TransportInterface(const ServerConfiguration &config, QObject *parent = 0); virtual ~TransportInterface() = 0; virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0; virtual void sendData(const QList &clients, const QByteArray &data) = 0; + void setConfiguration(const ServerConfiguration &config); + ServerConfiguration configuration() const; + signals: void clientConnected(const QUuid &clientId); void clientDisconnected(const QUuid &clientId); @@ -46,6 +51,9 @@ signals: public slots: virtual bool startServer() = 0; virtual bool stopServer() = 0; + +private: + ServerConfiguration m_config; }; } diff --git a/server/webserver.cpp b/server/webserver.cpp index 745504c1..bfdd3937 100644 --- a/server/webserver.cpp +++ b/server/webserver.cpp @@ -95,20 +95,18 @@ namespace guhserver { * * \sa ServerManager */ -WebServer::WebServer(const QHostAddress &host, const uint &port, const QString &publicFolder, bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) : +WebServer::WebServer(const WebServerConfiguration &configuration, const QSslConfiguration &sslConfiguration, QObject *parent) : QTcpServer(parent), m_avahiService(NULL), - m_host(host), - m_port(port), - m_webinterfaceDir(publicFolder), + m_configuration(configuration), m_sslConfiguration(sslConfiguration), - m_useSsl(sslEnabled), m_enabled(false) { if (QCoreApplication::instance()->organizationName() == "guh-test") { - m_webinterfaceDir = QDir(QCoreApplication::applicationDirPath()); - qCWarning(dcWebServer) << "Using public folder" << m_webinterfaceDir.path(); + m_configuration.publicFolder = QCoreApplication::applicationDirPath(); + qCWarning(dcWebServer) << "Using public folder" << m_configuration.publicFolder; } + qCDebug(dcWebServer) << "Using public folder" << m_configuration.publicFolder; #ifndef TESTING_ENABLED m_avahiService = new QtAvahiService(this); @@ -158,7 +156,7 @@ bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName) } // make shore the file is in the public directory - if (!file.canonicalFilePath().startsWith(m_webinterfaceDir.path())) { + if (!file.canonicalFilePath().startsWith(m_configuration.publicFolder)) { qCWarning(dcWebServer) << "requested file" << file.fileName() << "is outside the public folder."; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setClientId(m_clientList.key(socket)); @@ -189,7 +187,7 @@ QString WebServer::fileName(const QString &query) fileName = query; } - return m_webinterfaceDir.path() + fileName; + return m_configuration.publicFolder + "/" + fileName; } HttpReply *WebServer::processIconRequest(const QString &fileName) @@ -255,7 +253,7 @@ void WebServer::incomingConnection(qintptr socketDescriptor) qCDebug(dcConnection) << QString("Webserver client %1:%2 connected").arg(socket->peerAddress().toString()).arg(socket->peerPort()); - if (m_useSsl) { + if (m_configuration.sslEnabled) { // configure client connection socket->setSslConfiguration(m_sslConfiguration); connect(socket, SIGNAL(encrypted()), this, SLOT(onEncrypted())); @@ -365,7 +363,7 @@ void WebServer::readClient() qCDebug(dcWebServer) << "server XML request call"; HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/xml"); - reply->setPayload(createServerXmlDocument(m_host)); + reply->setPayload(createServerXmlDocument(m_configuration.address)); reply->setClientId(clientId); sendHttpReply(reply); reply->deleteLater(); @@ -376,8 +374,8 @@ void WebServer::readClient() // request for a file... if (request.method() == HttpRequest::Get) { // check if the webinterface dir does exist, otherwise a filerequest is not relevant - if (!m_webinterfaceDir.exists()) { - qCWarning(dcWebServer) << "webinterface folder" << m_webinterfaceDir.path() << "does not exist."; + if (!QDir(m_configuration.publicFolder).exists()) { + qCWarning(dcWebServer) << "webinterface folder" << m_configuration.publicFolder << "does not exist."; HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound); reply->setClientId(clientId); sendHttpReply(reply); @@ -489,41 +487,34 @@ void WebServer::onAvahiServiceStateChanged(const QtAvahiService::QtAvahiServiceS } /*! Returns true if this \l{WebServer} could be reconfigured with the given \a address and \a port. */ -bool WebServer::reconfigureServer(const QHostAddress &address, const uint &port) +void WebServer::reconfigureServer(const WebServerConfiguration &config) { - if (m_host == address && m_port == (qint16)port && isListening()) - return true; + if (m_configuration.address == config.address && + m_configuration.port == config.port && + m_configuration.sslEnabled == config.sslEnabled && + m_configuration.authenticationEnabled == config.authenticationEnabled && + m_configuration.publicFolder == config.publicFolder && + isListening()) + return; stopServer(); - - if (!listen(address, port)) { - qCWarning(dcConnection()) << "Webserver could not listen on" << serverAddress().toString() << m_port; - qCDebug(dcWebServer()) << "Restart server with old configuration."; - startServer(); - return false; - } - - close(); - m_host = address; - m_port = port; + m_configuration = config; startServer(); - - return true; } /*! Returns true if this \l{WebServer} started successfully. */ bool WebServer::startServer() { - if (!listen(m_host, m_port)) { - qCWarning(dcConnection) << "Webserver could not listen on" << serverAddress().toString() << m_port; + if (!listen(m_configuration.address, m_configuration.port)) { + qCWarning(dcConnection) << "Webserver could not listen on" << m_configuration.address.toString() << m_configuration.port; m_enabled = false; return false; } - if (m_useSsl) { - qCDebug(dcConnection) << "Started webserver on" << QString("https://%1:%2").arg(m_host.toString()).arg(m_port); + if (m_configuration.sslEnabled) { + qCDebug(dcConnection) << "Started webserver on" << QString("https://%1:%2").arg(m_configuration.address.toString()).arg(m_configuration.port); } else { - qCDebug(dcConnection) << "Started webserver on" << QString("http://%1:%2").arg(m_host.toString()).arg(m_port); + qCDebug(dcConnection) << "Started webserver on" << QString("http://%1:%2").arg(m_configuration.address.toString()).arg(m_configuration.port); } #ifndef TESTING_ENABLED @@ -534,7 +525,7 @@ bool WebServer::startServer() txt.insert("manufacturer", "guh GmbH"); txt.insert("uuid", GuhCore::instance()->configuration()->serverUuid().toString()); txt.insert("name", GuhCore::instance()->configuration()->serverName()); - m_avahiService->registerService("guhIO", m_port, "_http._tcp", txt); + m_avahiService->registerService("guhIO", m_configuration.port, "_http._tcp", txt); #endif m_enabled = true; @@ -580,13 +571,13 @@ QByteArray WebServer::createServerXmlDocument(QHostAddress address) writer.writeTextElement("minor", "1"); writer.writeEndElement(); // specVersion - if (m_useSsl) { - writer.writeTextElement("URLBase", "https://" + address.toString() + ":" + QString::number(m_port)); + if (m_configuration.sslEnabled) { + writer.writeTextElement("URLBase", "https://" + address.toString() + ":" + QString::number(m_configuration.port)); } else { - writer.writeTextElement("URLBase", "http://" + address.toString() + ":" + QString::number(m_port)); + writer.writeTextElement("URLBase", "http://" + address.toString() + ":" + QString::number(m_configuration.port)); } - if (m_useSsl) { + if (m_configuration.sslEnabled) { writer.writeTextElement("websocketURL", "wss://" + address.toString() + ":" + QString::number(websocketPort)); } else { writer.writeTextElement("websocketURL", "ws://" + address.toString() + ":" + QString::number(websocketPort)); diff --git a/server/webserver.h b/server/webserver.h index 1c2cd374..71c9f86f 100644 --- a/server/webserver.h +++ b/server/webserver.h @@ -36,6 +36,8 @@ #include "network/avahi/qtavahiservice.h" +#include "guhconfiguration.h" + // Note: Hypertext Transfer Protocol (HTTP/1.1) from the Internet Engineering Task Force (IETF): // https://tools.ietf.org/html/rfc7231 @@ -72,7 +74,7 @@ class WebServer : public QTcpServer { Q_OBJECT public: - explicit WebServer(const QHostAddress &host, const uint &port, const QString &publicFolder, bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = 0); + explicit WebServer(const WebServerConfiguration &configuration, const QSslConfiguration &sslConfiguration, QObject *parent = 0); ~WebServer(); void sendHttpReply(HttpReply *reply); @@ -84,12 +86,9 @@ private: QtAvahiService *m_avahiService; - QHostAddress m_host; - qint16 m_port; - QDir m_webinterfaceDir; + WebServerConfiguration m_configuration; QSslConfiguration m_sslConfiguration; - bool m_useSsl; bool m_enabled; @@ -116,7 +115,7 @@ private slots: void onAvahiServiceStateChanged(const QtAvahiService::QtAvahiServiceState &state); public slots: - bool reconfigureServer(const QHostAddress &address, const uint &port); + void reconfigureServer(const WebServerConfiguration &config); bool startServer(); bool stopServer(); diff --git a/server/websocketserver.cpp b/server/websocketserver.cpp index 7882ff2f..c9c7d67f 100644 --- a/server/websocketserver.cpp +++ b/server/websocketserver.cpp @@ -60,13 +60,10 @@ namespace guhserver { * * \sa ServerManager */ -WebSocketServer::WebSocketServer(const QHostAddress &address, const uint &port, const bool &sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent) : - TransportInterface(parent), +WebSocketServer::WebSocketServer(const ServerConfiguration &configuration, const QSslConfiguration &sslConfiguration, QObject *parent) : + TransportInterface(configuration, parent), m_server(0), - m_host(address), - m_port(port), m_sslConfiguration(sslConfiguration), - m_useSsl(sslEnabled), m_enabled(false) { #ifndef TESTING_ENABLED @@ -180,41 +177,21 @@ void WebSocketServer::onAvahiServiceStateChanged(const QtAvahiService::QtAvahiSe } /*! Returns true if this \l{WebSocketServer} could be reconfigured with the given \a address and \a port. */ -bool WebSocketServer::reconfigureServer(const QHostAddress &address, const uint &port) +void WebSocketServer::reconfigureServer(const ServerConfiguration &config) { - if (m_host == address && m_port == (qint16)port && m_server->isListening()) { + if (configuration() == config && m_server->isListening()) { qCDebug(dcWebSocketServer()) << "Configuration unchanged. Not restarting the server."; - return true; + return; } stopServer(); qCDebug(dcWebSocketServer()) << "Stopped server for reconfiguration."; - QWebSocketServer *server; - if (m_useSsl) { - server = new QWebSocketServer("guh", QWebSocketServer::SecureMode, this); - server->setSslConfiguration(m_sslConfiguration); - } else { - server = new QWebSocketServer("guh", QWebSocketServer::NonSecureMode, this); - } - - if(!server->listen(address, port)) { - qCWarning(dcConnection) << "Websocket server error: can not listen on" << address.toString() << port; - delete server; - // Restart the server with the old configuration - qCDebug(dcWebSocketServer()) << "Restart server with old configuration."; - startServer(); - return false; - } - // Remove the test server.. - server->close(); - delete server; + setConfiguration(config); // Start server with new configuration - m_host = address; - m_port = port; qCDebug(dcWebSocketServer()) << "Restart server with new configuration."; - return startServer(); + startServer(); } /*! Returns true if this \l{WebSocketServer} started successfully. @@ -223,7 +200,7 @@ bool WebSocketServer::reconfigureServer(const QHostAddress &address, const uint */ bool WebSocketServer::startServer() { - if (m_useSsl) { + if (configuration().sslEnabled) { m_server = new QWebSocketServer("guh", QWebSocketServer::SecureMode, this); m_server->setSslConfiguration(m_sslConfiguration); } else { @@ -232,15 +209,15 @@ bool WebSocketServer::startServer() connect (m_server, &QWebSocketServer::newConnection, this, &WebSocketServer::onClientConnected); connect (m_server, &QWebSocketServer::acceptError, this, &WebSocketServer::onServerError); - if (!m_server->listen(m_host, m_port)) { - qCWarning(dcConnection) << "Websocket server" << m_server->serverName() << QString("could not listen on %1:%2").arg(m_server->serverAddress().toString()).arg(m_port); + if (!m_server->listen(configuration().address, configuration().port)) { + qCWarning(dcConnection) << "Websocket server" << m_server->serverName() << QString("could not listen on %1:%2").arg(m_server->serverAddress().toString()).arg(configuration().port); return false; } if (m_server->secureMode() == QWebSocketServer::NonSecureMode) { - qCDebug(dcConnection) << "Started websocket server" << m_server->serverName() << QString("on ws://%1:%2").arg(m_server->serverAddress().toString()).arg(m_port); + qCDebug(dcConnection) << "Started websocket server" << m_server->serverName() << QString("on ws://%1:%2").arg(m_server->serverAddress().toString()).arg(configuration().port); } else { - qCDebug(dcConnection) << "Started websocket server" << m_server->serverName() << QString("on wss://%1:%2").arg(m_server->serverAddress().toString()).arg(m_port); + qCDebug(dcConnection) << "Started websocket server" << m_server->serverName() << QString("on wss://%1:%2").arg(m_server->serverAddress().toString()).arg(configuration().port); } #ifndef TESTING_ENABLED @@ -251,7 +228,7 @@ bool WebSocketServer::startServer() txt.insert("manufacturer", "guh GmbH"); txt.insert("uuid", GuhCore::instance()->configuration()->serverUuid().toString()); txt.insert("name", GuhCore::instance()->configuration()->serverName()); - m_avahiService->registerService("guhIO", m_port, "_ws._tcp", txt); + m_avahiService->registerService("guhIO", configuration().port, "_ws._tcp", txt); #endif return true; @@ -272,9 +249,11 @@ bool WebSocketServer::stopServer() client->close(QWebSocketProtocol::CloseCodeNormal, "Stop server"); } - m_server->close(); - delete m_server; - m_server = 0; + if (m_server) { + m_server->close(); + delete m_server; + m_server = nullptr; + } return true; } diff --git a/server/websocketserver.h b/server/websocketserver.h index bd0e7dc0..a71ce3bb 100644 --- a/server/websocketserver.h +++ b/server/websocketserver.h @@ -42,7 +42,7 @@ class WebSocketServer : public TransportInterface { Q_OBJECT public: - explicit WebSocketServer(const QHostAddress &address, const uint &port, const bool &sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = 0); + explicit WebSocketServer(const ServerConfiguration &configuration, const QSslConfiguration &sslConfiguration, QObject *parent = 0); ~WebSocketServer(); void sendData(const QUuid &clientId, const QByteArray &data) override; @@ -54,11 +54,7 @@ private: QtAvahiService *m_avahiService; - QHostAddress m_host; - qint16 m_port; - QSslConfiguration m_sslConfiguration; - bool m_useSsl; bool m_enabled; @@ -74,7 +70,7 @@ private slots: void onAvahiServiceStateChanged(const QtAvahiService::QtAvahiServiceState &state); public slots: - bool reconfigureServer(const QHostAddress &address, const uint &port); + void reconfigureServer(const ServerConfiguration &config); bool startServer() override; bool stopServer() override; }; diff --git a/tests/auto/api.json b/tests/auto/api.json index 6f949828..0a21dfaf 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -49 +50 { "methods": { "Actions.ExecuteAction": { @@ -31,6 +31,33 @@ } } }, + "Configuration.DeleteTcpServerConfiguration": { + "description": "Delete a TCP interface of the server. Note: if you are deleting the configuration for the interface you are currently connected to, the connection will be dropped.", + "params": { + "id": "String" + }, + "returns": { + "configurationError": "$ref:ConfigurationError" + } + }, + "Configuration.DeleteWebServerConfiguration": { + "description": "Delete a WebServer interface of the server.", + "params": { + "id": "String" + }, + "returns": { + "configurationError": "$ref:ConfigurationError" + } + }, + "Configuration.DeleteWebSocketServerConfiguration": { + "description": "Delete a WebSocket Server interface of the server. Note: if you are deleting the configuration for the interface you are currently connected to, the connection will be dropped.", + "params": { + "id": "String" + }, + "returns": { + "configurationError": "$ref:ConfigurationError" + } + }, "Configuration.GetAvailableLanguages": { "description": "Returns a list of locale codes available for the server. i.e. en_US, de_AT", "params": { @@ -53,21 +80,15 @@ "serverUuid": "Uuid", "timeZone": "String" }, - "sslConfiguration": { - "enabled": "Bool" - }, - "tcpServerConfiguration": { - "host": "String", - "port": "Uint" - }, - "webServerConfiguration": { - "host": "String", - "port": "Uint" - }, - "webSocketServerConfiguration": { - "host": "String", - "port": "Uint" - } + "tcpServerConfigurations": [ + "$ref:ServerConfiguration" + ], + "webServerConfigurations": [ + "$ref:WebServerConfiguration" + ], + "webSocketServerConfigurations": [ + "$ref:ServerConfiguration" + ] } }, "Configuration.GetTimeZones": { @@ -99,10 +120,9 @@ } }, "Configuration.SetTcpServerConfiguration": { - "description": "Configure the TCP interface of the server. Note: if you are using the TCP server for this call you will loose the connection.", + "description": "Configure a TCP interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Note: if you are changing the configuration for the interface you are currently connected to, the connection will be dropped.", "params": { - "host": "String", - "port": "Uint" + "configuration": "$ref:ServerConfiguration" }, "returns": { "configurationError": "$ref:ConfigurationError" @@ -118,20 +138,18 @@ } }, "Configuration.SetWebServerConfiguration": { - "description": "Configure the web server of the server.", + "description": "Configure a WebServer interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added.", "params": { - "host": "String", - "port": "Uint" + "configuration": "$ref:WebServerConfiguration" }, "returns": { "configurationError": "$ref:ConfigurationError" } }, "Configuration.SetWebSocketServerConfiguration": { - "description": "Configure the web socket interface of the server. Note: if you are using the web socket server for this call you will loose the connection.", + "description": "Configure a WebSocket Server interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Note: if you are changing the configuration for the interface you are currently connected to, the connection will be dropped.", "params": { - "host": "String", - "port": "Uint" + "configuration": "$ref:ServerConfiguration" }, "returns": { "configurationError": "$ref:ConfigurationError" @@ -905,6 +923,7 @@ "ConfigurationErrorNoError", "ConfigurationErrorInvalidTimeZone", "ConfigurationErrorInvalidStationName", + "ConfigurationErrorInvalidId", "ConfigurationErrorInvalidPort", "ConfigurationErrorInvalidHostAddress", "ConfigurationErrorBluetoothHardwareNotAvailable", @@ -1254,6 +1273,13 @@ "RuleErrorContainsEventBasesAction", "RuleErrorNoExitActions" ], + "ServerConfiguration": { + "address": "String", + "authenticationEnabled": "Bool", + "id": "String", + "port": "Uint", + "sslEnabled": "Bool" + }, "SetupMethod": [ "SetupMethodJustAdd", "SetupMethodDisplayPin", diff --git a/tests/auto/guhtestbase.cpp b/tests/auto/guhtestbase.cpp index 92d3345a..257f37e3 100644 --- a/tests/auto/guhtestbase.cpp +++ b/tests/auto/guhtestbase.cpp @@ -118,6 +118,9 @@ GuhTestBase::GuhTestBase(QObject *parent) : m_mockDevice2Port = 7331 + (qrand() % 1000); QCoreApplication::instance()->setOrganizationName("guh-test"); + QSignalSpy spy(GuhCore::instance(), SIGNAL(initialized())); + spy.wait(); + GuhCore::instance()->userManager()->removeUser("dummy@guh.io"); GuhCore::instance()->userManager()->createUser("dummy@guh.io", "DummyPW1!"); m_apiToken = GuhCore::instance()->userManager()->authenticate("dummy@guh.io", "DummyPW1!", "testcase"); @@ -476,6 +479,8 @@ void GuhTestBase::restartServer() { // Destroy and recreate the core instance... GuhCore::instance()->destroy(); + QSignalSpy coreSpy(GuhCore::instance(), SIGNAL(initialized())); + coreSpy.wait(); QSignalSpy spy(GuhCore::instance()->deviceManager(), SIGNAL(loaded())); spy.wait(); m_mockTcpServer = MockTcpServer::servers().first(); diff --git a/tests/auto/mocktcpserver.cpp b/tests/auto/mocktcpserver.cpp index 53728a00..10c5c2d8 100644 --- a/tests/auto/mocktcpserver.cpp +++ b/tests/auto/mocktcpserver.cpp @@ -32,7 +32,7 @@ using namespace guhserver; QList MockTcpServer::s_allServers; MockTcpServer::MockTcpServer(QObject *parent): - TransportInterface(parent) + TransportInterface(ServerConfiguration(), parent) { s_allServers.append(this); }